10 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
14 use PVE
::JSONSchema
qw(get_standard_option);
15 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach);
17 use PVE
::AccessControl
;
22 my $nodename = PVE
::INotify
::nodename
();
24 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
26 PVE
::JSONSchema
::register_format
('pve-lxc-network', \
&verify_lxc_network
);
27 sub verify_lxc_network
{
28 my ($value, $noerr) = @_;
30 return $value if parse_lxc_network
($value);
32 return undef if $noerr;
34 die "unable to parse network setting\n";
37 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', \
&verify_ct_mountpoint
);
38 sub verify_ct_mountpoint
{
39 my ($value, $noerr) = @_;
41 return $value if parse_ct_mountpoint
($value);
43 return undef if $noerr;
45 die "unable to parse CT mountpoint options\n";
48 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
49 type
=> 'string', format
=> 'pve-ct-mountpoint',
50 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
51 description
=> "Use volume as container root.",
55 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
56 description
=> "The name of the snapshot.",
57 type
=> 'string', format
=> 'pve-configid',
65 description
=> "Lock/unlock the VM.",
66 enum
=> [qw(migrate backup snapshot rollback)],
71 description
=> "Specifies whether a VM will be started during system bootup.",
74 startup
=> get_standard_option
('pve-startup-order'),
78 description
=> "Enable/disable Template.",
84 enum
=> ['amd64', 'i386'],
85 description
=> "OS architecture type.",
91 enum
=> ['debian', 'ubuntu', 'centos', 'archlinux'],
92 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
97 description
=> "Attach a console device (/dev/console) to the container.",
103 description
=> "Specify the number of tty available to the container",
111 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.",
119 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.",
127 description
=> "Amount of RAM for the VM in MB.",
134 description
=> "Amount of SWAP for the VM in MB.",
140 description
=> "Set a host name for the container.",
147 description
=> "Container description. Only used on the configuration web interface.",
152 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 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.",
159 rootfs
=> get_standard_option
('pve-ct-rootfs'),
162 type
=> 'string', format
=> 'pve-configid',
164 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
168 description
=> "Timestamp for snapshots.",
174 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).",
176 enum
=> ['shell', 'console', 'tty'],
181 my $valid_lxc_conf_keys = {
185 'lxc.haltsignal' => 1,
186 'lxc.rebootsignal' => 1,
187 'lxc.stopsignal' => 1,
189 'lxc.network.type' => 1,
190 'lxc.network.flags' => 1,
191 'lxc.network.link' => 1,
192 'lxc.network.mtu' => 1,
193 'lxc.network.name' => 1,
194 'lxc.network.hwaddr' => 1,
195 'lxc.network.ipv4' => 1,
196 'lxc.network.ipv4.gateway' => 1,
197 'lxc.network.ipv6' => 1,
198 'lxc.network.ipv6.gateway' => 1,
199 'lxc.network.script.up' => 1,
200 'lxc.network.script.down' => 1,
202 'lxc.console.logfile' => 1,
205 'lxc.devttydir' => 1,
206 'lxc.hook.autodev' => 1,
210 'lxc.mount.entry' => 1,
211 'lxc.mount.auto' => 1,
213 'lxc.rootfs.mount' => 1,
214 'lxc.rootfs.options' => 1,
218 'lxc.aa_profile' => 1,
219 'lxc.aa_allow_incomplete' => 1,
220 'lxc.se_context' => 1,
223 'lxc.hook.pre-start' => 1,
224 'lxc.hook.pre-mount' => 1,
225 'lxc.hook.mount' => 1,
226 'lxc.hook.start' => 1,
227 'lxc.hook.post-stop' => 1,
228 'lxc.hook.clone' => 1,
229 'lxc.hook.destroy' => 1,
232 'lxc.start.auto' => 1,
233 'lxc.start.delay' => 1,
234 'lxc.start.order' => 1,
236 'lxc.environment' => 1,
243 my $MAX_LXC_NETWORKS = 10;
244 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
245 $confdesc->{"net$i"} = {
247 type
=> 'string', format
=> 'pve-lxc-network',
248 description
=> "Specifies network interfaces for the container.\n\n".
249 "The string should have the follow format:\n\n".
250 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
251 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
252 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
253 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
257 my $MAX_MOUNT_POINTS = 10;
258 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
259 $confdesc->{"mp$i"} = {
261 type
=> 'string', format
=> 'pve-ct-mountpoint',
262 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
263 description
=> "Use volume as container mount point (experimental feature).",
268 sub write_pct_config
{
269 my ($filename, $conf) = @_;
271 delete $conf->{snapstate
}; # just to be sure
273 my $generate_raw_config = sub {
278 # add description as comment to top of file
279 my $descr = $conf->{description
} || '';
280 foreach my $cl (split(/\n/, $descr)) {
281 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
284 foreach my $key (sort keys %$conf) {
285 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
286 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
287 $raw .= "$key: $conf->{$key}\n";
290 if (my $lxcconf = $conf->{lxc
}) {
291 foreach my $entry (@$lxcconf) {
292 my ($k, $v) = @$entry;
300 my $raw = &$generate_raw_config($conf);
302 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
303 $raw .= "\n[$snapname]\n";
304 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
311 my ($key, $value) = @_;
313 die "unknown setting '$key'\n" if !$confdesc->{$key};
315 my $type = $confdesc->{$key}->{type
};
317 if (!defined($value)) {
318 die "got undefined value\n";
321 if ($value =~ m/[\n\r]/) {
322 die "property contains a line feed\n";
325 if ($type eq 'boolean') {
326 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
327 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
328 die "type check ('boolean') failed - got '$value'\n";
329 } elsif ($type eq 'integer') {
330 return int($1) if $value =~ m/^(\d+)$/;
331 die "type check ('integer') failed - got '$value'\n";
332 } elsif ($type eq 'number') {
333 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
334 die "type check ('number') failed - got '$value'\n";
335 } elsif ($type eq 'string') {
336 if (my $fmt = $confdesc->{$key}->{format
}) {
337 PVE
::JSONSchema
::check_format
($fmt, $value);
346 sub parse_pct_config
{
347 my ($filename, $raw) = @_;
349 return undef if !defined($raw);
352 digest
=> Digest
::SHA
::sha1_hex
($raw),
356 $filename =~ m
|/lxc/(\d
+).conf
$|
357 || die "got strange filename '$filename'";
365 my @lines = split(/\n/, $raw);
366 foreach my $line (@lines) {
367 next if $line =~ m/^\s*$/;
369 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
371 $conf->{description
} = $descr if $descr;
373 $conf = $res->{snapshots
}->{$section} = {};
377 if ($line =~ m/^\#(.*)\s*$/) {
378 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
382 if ($line =~ m/^(lxc\.[a-z0-9_\.]+)(:|\s*=)\s*(.*?)\s*$/) {
385 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
386 push @{$conf->{lxc
}}, [$key, $value];
388 warn "vm $vmid - unable to parse config: $line\n";
390 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
391 $descr .= PVE
::Tools
::decode_text
($2);
392 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
393 $conf->{snapstate
} = $1;
394 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
397 eval { $value = check_type
($key, $value); };
398 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
399 $conf->{$key} = $value;
401 warn "vm $vmid - unable to parse config: $line\n";
405 $conf->{description
} = $descr if $descr;
407 delete $res->{snapstate
}; # just to be sure
413 my $vmlist = PVE
::Cluster
::get_vmlist
();
415 return $res if !$vmlist || !$vmlist->{ids
};
416 my $ids = $vmlist->{ids
};
418 foreach my $vmid (keys %$ids) {
419 next if !$vmid; # skip CT0
420 my $d = $ids->{$vmid};
421 next if !$d->{node
} || $d->{node
} ne $nodename;
422 next if !$d->{type
} || $d->{type
} ne 'lxc';
423 $res->{$vmid}->{type
} = 'lxc';
428 sub cfs_config_path
{
429 my ($vmid, $node) = @_;
431 $node = $nodename if !$node;
432 return "nodes/$node/lxc/$vmid.conf";
436 my ($vmid, $node) = @_;
438 my $cfspath = cfs_config_path
($vmid, $node);
439 return "/etc/pve/$cfspath";
443 my ($vmid, $node) = @_;
445 $node = $nodename if !$node;
446 my $cfspath = cfs_config_path
($vmid, $node);
448 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
449 die "container $vmid does not exists\n" if !defined($conf);
455 my ($vmid, $conf) = @_;
457 my $dir = "/etc/pve/nodes/$nodename/lxc";
460 write_config
($vmid, $conf);
466 unlink config_file
($vmid, $nodename);
470 my ($vmid, $conf) = @_;
472 my $cfspath = cfs_config_path
($vmid);
474 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
477 # flock: we use one file handle per process, so lock file
478 # can be called multiple times and succeeds for the same process.
480 my $lock_handles = {};
481 my $lockdir = "/run/lock/lxc";
486 return "$lockdir/pve-config-${vmid}.lock";
490 my ($vmid, $timeout) = @_;
492 $timeout = 10 if !$timeout;
495 my $filename = lock_filename
($vmid);
497 mkdir $lockdir if !-d
$lockdir;
499 my $lock_func = sub {
500 if (!$lock_handles->{$$}->{$filename}) {
501 my $fh = new IO
::File
(">>$filename") ||
502 die "can't open file - $!\n";
503 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
506 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
507 print STDERR
"trying to aquire lock...";
510 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
511 # try again on EINTR (see bug #273)
512 if ($success || ($! != EINTR
)) {
517 print STDERR
" failed\n";
518 die "can't aquire lock - $!\n";
521 print STDERR
" OK\n";
524 $lock_handles->{$$}->{$filename}->{refcount
}++;
527 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
530 die "can't lock file '$filename' - $err";
537 my $filename = lock_filename
($vmid);
539 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
540 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
541 if ($refcount <= 0) {
542 $lock_handles->{$$}->{$filename} = undef;
549 my ($vmid, $timeout, $code, @param) = @_;
553 lock_aquire
($vmid, $timeout);
554 eval { $res = &$code(@param) };
566 return defined($confdesc->{$name});
569 # add JSON properties for create and set function
570 sub json_config_properties
{
573 foreach my $opt (keys %$confdesc) {
574 next if $opt eq 'parent' || $opt eq 'snaptime';
575 next if $prop->{$opt};
576 $prop->{$opt} = $confdesc->{$opt};
582 sub json_config_properties_no_rootfs
{
585 foreach my $opt (keys %$confdesc) {
586 next if $prop->{$opt};
587 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
588 $prop->{$opt} = $confdesc->{$opt};
594 # container status helpers
596 sub list_active_containers
{
598 my $filename = "/proc/net/unix";
600 # similar test is used by lcxcontainers.c: list_active_containers
603 my $fh = IO
::File-
>new ($filename, "r");
606 while (defined(my $line = <$fh>)) {
607 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
609 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
620 # warning: this is slow
624 my $active_hash = list_active_containers
();
626 return 1 if defined($active_hash->{$vmid});
631 sub get_container_disk_usage
{
634 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
644 if (my ($fsid, $total, $used, $avail) = $line =~
645 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
653 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
662 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
664 my $active_hash = list_active_containers
();
666 foreach my $vmid (keys %$list) {
667 my $d = $list->{$vmid};
669 my $running = defined($active_hash->{$vmid});
671 $d->{status
} = $running ?
'running' : 'stopped';
673 my $cfspath = cfs_config_path
($vmid);
674 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
676 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
677 $d->{name
} =~ s/[\s]//g;
679 $d->{cpus
} = $conf->{cpulimit
} // 0;
682 my $res = get_container_disk_usage
($vmid);
683 $d->{disk
} = $res->{used
};
684 $d->{maxdisk
} = $res->{total
};
687 # use 4GB by default ??
688 if (my $rootfs = $conf->{rootfs
}) {
689 my $rootinfo = parse_ct_mountpoint
($rootfs);
690 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
692 $d->{maxdisk
} = 4*1024*1024*1024;
698 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
699 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
710 $d->{template
} = is_template
($conf);
713 foreach my $vmid (keys %$list) {
714 my $d = $list->{$vmid};
715 next if $d->{status
} ne 'running';
717 $d->{uptime
} = 100; # fixme:
719 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
720 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
722 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
723 my @bytes = split(/\n/, $blkio_bytes);
724 foreach my $byte (@bytes) {
725 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
726 $d->{diskread
} = $2 if $key eq 'Read';
727 $d->{diskwrite
} = $2 if $key eq 'Write';
735 my $parse_size = sub {
738 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
739 my ($size, $unit) = ($1, $3);
742 $size = $size * 1024;
743 } elsif ($unit eq 'M') {
744 $size = $size * 1024 * 1024;
745 } elsif ($unit eq 'G') {
746 $size = $size * 1024 * 1024 * 1024;
752 sub parse_ct_mountpoint
{
759 foreach my $p (split (/,/, $data)) {
760 next if $p =~ m/^\s*$/;
762 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
763 my ($k, $v) = ($1, $2);
764 return undef if defined($res->{$k});
767 if (!$res->{volume
} && $p !~ m/=/) {
775 return undef if !defined($res->{volume
});
777 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
780 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
786 sub print_ct_mountpoint
{
787 my ($info, $nomp) = @_;
791 die "missing volume\n" if !$info->{volume
};
793 foreach my $o ('size', 'backup') {
794 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
796 $opts .= ",mp=$info->{mp}" if !$nomp;
798 return "$info->{volume}$opts";
801 sub print_lxc_network
{
804 die "no network name defined\n" if !$net->{name
};
806 my $res = "name=$net->{name}";
808 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
809 next if !defined($net->{$k});
810 $res .= ",$k=$net->{$k}";
816 sub parse_lxc_network
{
821 return $res if !$data;
823 foreach my $pv (split (/,/, $data)) {
824 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
831 $res->{type
} = 'veth';
832 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
837 sub read_cgroup_value
{
838 my ($group, $vmid, $name, $full) = @_;
840 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
842 return PVE
::Tools
::file_get_contents
($path) if $full;
844 return PVE
::Tools
::file_read_firstline
($path);
847 sub write_cgroup_value
{
848 my ($group, $vmid, $name, $value) = @_;
850 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
851 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
855 sub find_lxc_console_pids
{
859 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
862 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
865 my @args = split(/\0/, $cmdline);
867 # serach for lxc-console -n <vmid>
868 return if scalar(@args) != 3;
869 return if $args[1] ne '-n';
870 return if $args[2] !~ m/^\d+$/;
871 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
875 push @{$res->{$vmid}}, $pid;
887 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
889 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
891 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
896 my $ipv4_reverse_mask = [
932 # Note: we cannot use Net:IP, because that only allows strict
934 sub parse_ipv4_cidr
{
935 my ($cidr, $noerr) = @_;
937 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
938 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
941 return undef if $noerr;
943 die "unable to parse ipv4 address/mask\n";
949 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
952 sub update_lxc_config
{
953 my ($storage_cfg, $vmid, $conf) = @_;
955 my $dir = "/var/lib/lxc/$vmid";
957 if ($conf->{template
}) {
959 unlink "$dir/config";
966 die "missing 'arch' - internal error" if !$conf->{arch
};
967 $raw .= "lxc.arch = $conf->{arch}\n";
969 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
970 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
971 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
976 if (!has_dev_console
($conf)) {
977 $raw .= "lxc.console = none\n";
978 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
981 my $ttycount = get_tty_count
($conf);
982 $raw .= "lxc.tty = $ttycount\n";
984 my $utsname = $conf->{hostname
} || "CT$vmid";
985 $raw .= "lxc.utsname = $utsname\n";
987 my $memory = $conf->{memory
} || 512;
988 my $swap = $conf->{swap
} // 0;
990 my $lxcmem = int($memory*1024*1024);
991 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
993 my $lxcswap = int(($memory + $swap)*1024*1024);
994 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
996 if (my $cpulimit = $conf->{cpulimit
}) {
997 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
998 my $value = int(100000*$cpulimit);
999 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1002 my $shares = $conf->{cpuunits
} || 1024;
1003 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1005 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1006 $mountpoint->{mp
} = '/';
1007 my $volid = $mountpoint->{volume
};
1008 my $path = mountpoint_mount_path
($mountpoint, $storage_cfg);
1010 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1013 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1014 $path = "loop:$path" if $scfg->{path
};
1017 $raw .= "lxc.rootfs = $path\n";
1020 foreach my $k (keys %$conf) {
1021 next if $k !~ m/^net(\d+)$/;
1023 my $d = parse_lxc_network
($conf->{$k});
1025 $raw .= "lxc.network.type = veth\n";
1026 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1027 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1028 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1029 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1032 if (my $lxcconf = $conf->{lxc
}) {
1033 foreach my $entry (@$lxcconf) {
1034 my ($k, $v) = @$entry;
1035 $netcount++ if $k eq 'lxc.network.type';
1036 $raw .= "$k = $v\n";
1040 $raw .= "lxc.network.type = empty\n" if !$netcount;
1042 File
::Path
::mkpath
("$dir/rootfs");
1044 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1047 # verify and cleanup nameserver list (replace \0 with ' ')
1048 sub verify_nameserver_list
{
1049 my ($nameserver_list) = @_;
1052 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1053 PVE
::JSONSchema
::pve_verify_ip
($server);
1054 push @list, $server;
1057 return join(' ', @list);
1060 sub verify_searchdomain_list
{
1061 my ($searchdomain_list) = @_;
1064 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1065 # todo: should we add checks for valid dns domains?
1066 push @list, $server;
1069 return join(' ', @list);
1072 sub update_pct_config
{
1073 my ($vmid, $conf, $running, $param, $delete) = @_;
1081 my $pid = find_lxc_pid
($vmid);
1082 $rootdir = "/proc/$pid/root";
1085 if (defined($delete)) {
1086 foreach my $opt (@$delete) {
1087 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1088 die "unable to delete required option '$opt'\n";
1089 } elsif ($opt eq 'swap') {
1090 delete $conf->{$opt};
1091 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1092 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1093 delete $conf->{$opt};
1094 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1095 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1096 delete $conf->{$opt};
1097 push @nohotplug, $opt;
1099 } elsif ($opt =~ m/^net(\d)$/) {
1100 delete $conf->{$opt};
1103 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1104 } elsif ($opt =~ m/^mp(\d+)$/) {
1105 delete $conf->{$opt};
1106 push @nohotplug, $opt;
1108 } elsif ($opt eq 'rootfs') {
1113 write_config
($vmid, $conf) if $running;
1117 # There's no separate swap size to configure, there's memory and "total"
1118 # memory (iow. memory+swap). This means we have to change them together.
1119 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1120 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1121 if (defined($wanted_memory) || defined($wanted_swap)) {
1123 $wanted_memory //= ($conf->{memory
} || 512);
1124 $wanted_swap //= ($conf->{swap
} || 0);
1126 my $total = $wanted_memory + $wanted_swap;
1128 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1129 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1131 $conf->{memory
} = $wanted_memory;
1132 $conf->{swap
} = $wanted_swap;
1134 write_config
($vmid, $conf) if $running;
1137 foreach my $opt (keys %$param) {
1138 my $value = $param->{$opt};
1139 if ($opt eq 'hostname') {
1140 $conf->{$opt} = $value;
1141 } elsif ($opt eq 'onboot') {
1142 $conf->{$opt} = $value ?
1 : 0;
1143 } elsif ($opt eq 'startup') {
1144 $conf->{$opt} = $value;
1145 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1146 $conf->{$opt} = $value;
1147 push @nohotplug, $opt;
1149 } elsif ($opt eq 'nameserver') {
1150 my $list = verify_nameserver_list
($value);
1151 $conf->{$opt} = $list;
1152 push @nohotplug, $opt;
1154 } elsif ($opt eq 'searchdomain') {
1155 my $list = verify_searchdomain_list
($value);
1156 $conf->{$opt} = $list;
1157 push @nohotplug, $opt;
1159 } elsif ($opt eq 'cpulimit') {
1160 $conf->{$opt} = $value;
1161 push @nohotplug, $opt; # fixme: hotplug
1163 } elsif ($opt eq 'cpuunits') {
1164 $conf->{$opt} = $value;
1165 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1166 } elsif ($opt eq 'description') {
1167 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1168 } elsif ($opt =~ m/^net(\d+)$/) {
1170 my $net = parse_lxc_network
($value);
1172 $conf->{$opt} = print_lxc_network
($net);
1174 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1176 } elsif ($opt =~ m/^mp(\d+)$/) {
1177 $conf->{$opt} = $value;
1178 push @$new_disks, $opt;
1179 push @nohotplug, $opt;
1181 } elsif ($opt eq 'rootfs') {
1182 die "implement me: $opt";
1184 die "implement me: $opt";
1186 write_config
($vmid, $conf) if $running;
1189 if ($running && scalar(@nohotplug)) {
1190 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1194 my $storage_cfg = PVE
::Storage
::config
();
1195 create_disks
($storage_cfg, $vmid, $conf, $conf);
1196 mount_all
($vmid, $storage_cfg, $conf, $new_disks, 1);
1197 umount_all
($vmid, $storage_cfg, $conf, 0);
1201 sub has_dev_console
{
1204 return !(defined($conf->{console
}) && !$conf->{console
});
1210 return $conf->{tty
} // $confdesc->{tty
}->{default};
1216 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1219 sub get_console_command
{
1220 my ($vmid, $conf) = @_;
1222 my $cmode = get_cmode
($conf);
1224 if ($cmode eq 'console') {
1225 return ['lxc-console', '-n', $vmid, '-t', 0];
1226 } elsif ($cmode eq 'tty') {
1227 return ['lxc-console', '-n', $vmid];
1228 } elsif ($cmode eq 'shell') {
1229 return ['lxc-attach', '--clear-env', '-n', $vmid];
1231 die "internal error";
1235 sub get_primary_ips
{
1238 # return data from net0
1240 return undef if !defined($conf->{net0
});
1241 my $net = parse_lxc_network
($conf->{net0
});
1243 my $ipv4 = $net->{ip
};
1245 if ($ipv4 =~ /^(dhcp|manual)$/) {
1251 my $ipv6 = $net->{ip6
};
1253 if ($ipv6 =~ /^(dhcp|manual)$/) {
1260 return ($ipv4, $ipv6);
1264 sub destroy_lxc_container
{
1265 my ($storage_cfg, $vmid, $conf) = @_;
1267 foreach_mountpoint
($conf, sub {
1268 my ($ms, $mountpoint) = @_;
1269 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $mountpoint->{volume
});
1270 PVE
::Storage
::vdisk_free
($storage_cfg, $mountpoint->{volume
}) if $vmid == $owner;
1273 rmdir "/var/lib/lxc/$vmid/rootfs";
1274 unlink "/var/lib/lxc/$vmid/config";
1275 rmdir "/var/lib/lxc/$vmid";
1276 destroy_config
($vmid);
1278 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1279 #PVE::Tools::run_command($cmd);
1282 sub vm_stop_cleanup
{
1283 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1288 my $vollist = get_vm_volumes
($conf);
1289 my $loopdevlist = get_vm_volumes
($conf, 'rootfs');
1291 detach_loops
($storage_cfg, $loopdevlist);
1292 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1295 warn $@ if $@; # avoid errors - just warn
1298 my $safe_num_ne = sub {
1301 return 0 if !defined($a) && !defined($b);
1302 return 1 if !defined($a);
1303 return 1 if !defined($b);
1308 my $safe_string_ne = sub {
1311 return 0 if !defined($a) && !defined($b);
1312 return 1 if !defined($a);
1313 return 1 if !defined($b);
1319 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1321 if ($newnet->{type
} ne 'veth') {
1322 # for when there are physical interfaces
1323 die "cannot update interface of type $newnet->{type}";
1326 my $veth = "veth${vmid}i${netid}";
1327 my $eth = $newnet->{name
};
1329 if (my $oldnetcfg = $conf->{$opt}) {
1330 my $oldnet = parse_lxc_network
($oldnetcfg);
1332 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1333 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1335 PVE
::Network
::veth_delete
($veth);
1336 delete $conf->{$opt};
1337 write_config
($vmid, $conf);
1339 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1341 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1342 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1343 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1345 if ($oldnet->{bridge
}) {
1346 PVE
::Network
::tap_unplug
($veth);
1347 foreach (qw(bridge tag firewall)) {
1348 delete $oldnet->{$_};
1350 $conf->{$opt} = print_lxc_network
($oldnet);
1351 write_config
($vmid, $conf);
1354 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1355 foreach (qw(bridge tag firewall)) {
1356 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1358 $conf->{$opt} = print_lxc_network
($oldnet);
1359 write_config
($vmid, $conf);
1362 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1365 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1369 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1371 my $veth = "veth${vmid}i${netid}";
1372 my $vethpeer = $veth . "p";
1373 my $eth = $newnet->{name
};
1375 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1376 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1378 # attach peer in container
1379 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1380 PVE
::Tools
::run_command
($cmd);
1382 # link up peer in container
1383 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1384 PVE
::Tools
::run_command
($cmd);
1386 my $done = { type
=> 'veth' };
1387 foreach (qw(bridge tag firewall hwaddr name)) {
1388 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1390 $conf->{$opt} = print_lxc_network
($done);
1392 write_config
($vmid, $conf);
1395 sub update_ipconfig
{
1396 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1398 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1400 my $optdata = parse_lxc_network
($conf->{$opt});
1404 my $cmdargs = shift;
1405 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1407 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1409 my $change_ip_config = sub {
1410 my ($ipversion) = @_;
1412 my $family_opt = "-$ipversion";
1413 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1414 my $gw= "gw$suffix";
1415 my $ip= "ip$suffix";
1417 my $newip = $newnet->{$ip};
1418 my $newgw = $newnet->{$gw};
1419 my $oldip = $optdata->{$ip};
1421 my $change_ip = &$safe_string_ne($oldip, $newip);
1422 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1424 return if !$change_ip && !$change_gw;
1426 # step 1: add new IP, if this fails we cancel
1427 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1428 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1435 # step 2: replace gateway
1436 # If this fails we delete the added IP and cancel.
1437 # If it succeeds we save the config and delete the old IP, ignoring
1438 # errors. The config is then saved.
1439 # Note: 'ip route replace' can add
1442 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1445 # the route was not replaced, the old IP is still available
1446 # rollback (delete new IP) and cancel
1448 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1449 warn $@ if $@; # no need to die here
1454 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1455 # if the route was not deleted, the guest might have deleted it manually
1461 # from this point on we save the configuration
1462 # step 3: delete old IP ignoring errors
1463 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1464 # We need to enable promote_secondaries, otherwise our newly added
1465 # address will be removed along with the old one.
1468 if ($ipversion == 4) {
1469 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1470 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1471 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1473 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1475 warn $@ if $@; # no need to die here
1477 if ($ipversion == 4) {
1478 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1482 foreach my $property ($ip, $gw) {
1483 if ($newnet->{$property}) {
1484 $optdata->{$property} = $newnet->{$property};
1486 delete $optdata->{$property};
1489 $conf->{$opt} = print_lxc_network
($optdata);
1490 write_config
($vmid, $conf);
1491 $lxc_setup->setup_network($conf);
1494 &$change_ip_config(4);
1495 &$change_ip_config(6);
1499 # Internal snapshots
1501 # NOTE: Snapshot create/delete involves several non-atomic
1502 # action, and can take a long time.
1503 # So we try to avoid locking the file and use 'lock' variable
1504 # inside the config file instead.
1506 my $snapshot_copy_config = sub {
1507 my ($source, $dest) = @_;
1509 foreach my $k (keys %$source) {
1510 next if $k eq 'snapshots';
1511 next if $k eq 'snapstate';
1512 next if $k eq 'snaptime';
1513 next if $k eq 'vmstate';
1514 next if $k eq 'lock';
1515 next if $k eq 'digest';
1516 next if $k eq 'description';
1518 $dest->{$k} = $source->{$k};
1522 my $snapshot_prepare = sub {
1523 my ($vmid, $snapname, $comment) = @_;
1527 my $updatefn = sub {
1529 my $conf = load_config
($vmid);
1531 die "you can't take a snapshot if it's a template\n"
1532 if is_template
($conf);
1536 $conf->{lock} = 'snapshot';
1538 die "snapshot name '$snapname' already used\n"
1539 if defined($conf->{snapshots
}->{$snapname});
1541 my $storecfg = PVE
::Storage
::config
();
1542 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1544 $snap = $conf->{snapshots
}->{$snapname} = {};
1546 &$snapshot_copy_config($conf, $snap);
1548 $snap->{'snapstate'} = "prepare";
1549 $snap->{'snaptime'} = time();
1550 $snap->{'description'} = $comment if $comment;
1551 $conf->{snapshots
}->{$snapname} = $snap;
1553 write_config
($vmid, $conf);
1556 lock_container
($vmid, 10, $updatefn);
1561 my $snapshot_commit = sub {
1562 my ($vmid, $snapname) = @_;
1564 my $updatefn = sub {
1566 my $conf = load_config
($vmid);
1568 die "missing snapshot lock\n"
1569 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1571 die "snapshot '$snapname' does not exist\n"
1572 if !defined($conf->{snapshots
}->{$snapname});
1574 die "wrong snapshot state\n"
1575 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1576 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1578 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1579 delete $conf->{lock};
1580 $conf->{parent
} = $snapname;
1582 write_config
($vmid, $conf);
1585 lock_container
($vmid, 10 ,$updatefn);
1589 my ($feature, $conf, $storecfg, $snapname) = @_;
1593 foreach_mountpoint
($conf, sub {
1594 my ($ms, $mountpoint) = @_;
1596 return if $err; # skip further test
1598 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1600 # TODO: implement support for mountpoints
1601 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1605 return $err ?
0 : 1;
1608 sub snapshot_create
{
1609 my ($vmid, $snapname, $comment) = @_;
1611 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1613 my $conf = load_config
($vmid);
1615 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1616 my $running = check_running
($vmid);
1619 PVE
::Tools
::run_command
($cmd);
1622 my $storecfg = PVE
::Storage
::config
();
1623 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1624 my $volid = $rootinfo->{volume
};
1626 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1628 PVE
::Tools
::run_command
($cmd);
1631 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1632 &$snapshot_commit($vmid, $snapname);
1635 snapshot_delete
($vmid, $snapname, 1);
1640 sub snapshot_delete
{
1641 my ($vmid, $snapname, $force) = @_;
1647 my $updatefn = sub {
1649 $conf = load_config
($vmid);
1651 die "you can't delete a snapshot if vm is a template\n"
1652 if is_template
($conf);
1654 $snap = $conf->{snapshots
}->{$snapname};
1658 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1660 $snap->{snapstate
} = 'delete';
1662 write_config
($vmid, $conf);
1665 lock_container
($vmid, 10, $updatefn);
1667 my $storecfg = PVE
::Storage
::config
();
1669 my $del_snap = sub {
1673 if ($conf->{parent
} eq $snapname) {
1674 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1675 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1677 delete $conf->{parent
};
1681 delete $conf->{snapshots
}->{$snapname};
1683 write_config
($vmid, $conf);
1686 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1687 my $rootinfo = parse_ct_mountpoint
($rootfs);
1688 my $volid = $rootinfo->{volume
};
1691 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1695 if(!$err || ($err && $force)) {
1696 lock_container
($vmid, 10, $del_snap);
1698 die "Can't delete snapshot: $vmid $snapname $err\n";
1703 sub snapshot_rollback
{
1704 my ($vmid, $snapname) = @_;
1706 my $storecfg = PVE
::Storage
::config
();
1708 my $conf = load_config
($vmid);
1710 die "you can't rollback if vm is a template\n" if is_template
($conf);
1712 my $snap = $conf->{snapshots
}->{$snapname};
1714 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1716 my $rootfs = $snap->{rootfs
};
1717 my $rootinfo = parse_ct_mountpoint
($rootfs);
1718 my $volid = $rootinfo->{volume
};
1720 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1722 my $updatefn = sub {
1724 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1725 if $snap->{snapstate
};
1729 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1731 die "unable to rollback vm $vmid: vm is running\n"
1732 if check_running
($vmid);
1734 $conf->{lock} = 'rollback';
1738 # copy snapshot config to current config
1740 my $tmp_conf = $conf;
1741 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1742 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1743 delete $conf->{snaptime
};
1744 delete $conf->{snapname
};
1745 $conf->{parent
} = $snapname;
1747 write_config
($vmid, $conf);
1750 my $unlockfn = sub {
1751 delete $conf->{lock};
1752 write_config
($vmid, $conf);
1755 lock_container
($vmid, 10, $updatefn);
1757 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1759 lock_container
($vmid, 5, $unlockfn);
1762 sub template_create
{
1763 my ($vmid, $conf) = @_;
1765 my $storecfg = PVE
::Storage
::config
();
1767 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1768 my $volid = $rootinfo->{volume
};
1770 die "Template feature is not available for '$volid'\n"
1771 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1773 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1775 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1776 $rootinfo->{volume
} = $template_volid;
1777 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1779 write_config
($vmid, $conf);
1785 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1788 sub mountpoint_names
{
1791 my @names = ('rootfs');
1793 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1794 push @names, "mp$i";
1797 return $reverse ?
reverse @names : @names;
1800 sub foreach_mountpoint_full
{
1801 my ($conf, $reverse, $func) = @_;
1803 foreach my $key (mountpoint_names
($reverse)) {
1804 my $value = $conf->{$key};
1805 next if !defined($value);
1806 my $mountpoint = parse_ct_mountpoint
($value);
1807 $mountpoint->{mp
} = '/' if $key eq 'rootfs'; # just to be sure
1808 &$func($key, $mountpoint);
1812 sub foreach_mountpoint
{
1813 my ($conf, $func) = @_;
1815 foreach_mountpoint_full
($conf, 0, $func);
1818 sub foreach_mountpoint_reverse
{
1819 my ($conf, $func) = @_;
1821 foreach_mountpoint_full
($conf, 1, $func);
1824 sub loopdevices_list
{
1829 if ($line =~ m/^(\/dev\
/loop\d+)\s+\d\s+\d\s+\d\s+\d\s(\S+)$/) {
1830 $loopdev->{$1} = $2;
1834 PVE
::Tools
::run_command
(['losetup'], outfunc
=> $parser);
1839 sub blockdevices_list
{
1842 dir_glob_foreach
("/sys/dev/block/", '(\d+):(\d+)', sub {
1843 my (undef, $major, $minor) = @_;
1844 my $bdev = readlink("/sys/dev/block/$major:$minor");
1845 $bdev =~ s/\.\.\/\.\.\/devices\
/virtual\/block\
//\
/dev\//;
1846 $bdevs->{$bdev}->{major
} = $major;
1847 $bdevs->{$bdev}->{minor
} = $minor;
1853 my ($loopdevs, $path) = @_;
1855 foreach my $dev (keys %$loopdevs){
1856 return $dev if $loopdevs->{$dev} eq $path;
1860 sub check_ct_modify_config_perm
{
1861 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1863 return 1 if $authuser ne 'root@pam';
1865 foreach my $opt (@$key_list) {
1867 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1868 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1869 } elsif ($opt eq 'disk') {
1870 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1871 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1872 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1873 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1874 $opt eq 'searchdomain' || $opt eq 'hostname') {
1875 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1877 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1885 my ($storage_cfg, $vollist, $snapname) = @_;
1889 foreach my $volid (@$vollist) {
1891 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1892 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1894 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1895 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1897 if ($format eq 'raw' && $scfg->{path
}) {
1898 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
1903 $loopdev = $line if $line =~m
|^/dev/loop\d
+$|;
1904 $loopdevs->{$loopdev} = $path;
1907 PVE
::Tools
::run_command
(['losetup', '--find', '--show', $path], outfunc
=> $parser);
1915 my ($storage_cfg, $vollist, $snapname) = @_;
1917 my $loopdevs = loopdevices_list
();
1919 foreach my $volid (@$vollist) {
1921 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1922 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1924 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1925 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1927 if ($format eq 'raw' && $scfg->{path
}) {
1928 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
1929 foreach my $dev (keys %$loopdevs){
1930 PVE
::Tools
::run_command
(['losetup', '-d', $dev]) if $loopdevs->{$dev} eq $path;
1937 my ($vmid, $storage_cfg, $conf, $noerr, $loopdevs) = @_;
1939 $loopdevs ||= loopdevices_list
();
1941 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1942 my $volid_list = get_vm_volumes
($conf);
1944 foreach_mountpoint_reverse
($conf, sub {
1945 my ($ms, $mountpoint) = @_;
1947 my $volid = $mountpoint->{volume
};
1948 my $mount = $mountpoint->{mp
};
1950 return if !$volid || !$mount;
1952 my $mount_path = "$rootdir/$mount";
1953 $mount_path =~ s!/+!/!g;
1955 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
1958 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
1969 PVE
::LXC
::detach_loops
($storage_cfg, $volid_list);
1973 my ($vmid, $storage_cfg, $conf, $mkdirs) = @_;
1975 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1977 my $volid_list = get_vm_volumes
($conf);
1978 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
1982 $loopdevs = attach_loops
($storage_cfg, $volid_list);
1984 foreach_mountpoint
($conf, sub {
1985 my ($ms, $mountpoint) = @_;
1987 my $volid = $mountpoint->{volume
};
1988 my $mount = $mountpoint->{mp
};
1990 return if !$volid || !$mount;
1992 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
1993 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1994 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1996 die "unable to mount base volume - internal error" if $isBase;
1998 File
::Path
::make_path
"$rootdir/$mount" if $mkdirs;
1999 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg, $loopdevs);
2003 warn "mounting container failed - $err";
2004 umount_all
($vmid, $storage_cfg, $conf, 1);
2007 return wantarray ?
($rootdir, $loopdevs) : $rootdir;
2011 sub mountpoint_mount_path
{
2012 my ($mountpoint, $storage_cfg, $loopdevs, $snapname) = @_;
2014 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $loopdevs, $snapname);
2017 # use $rootdir = undef to just return the corresponding mount path
2018 sub mountpoint_mount
{
2019 my ($mountpoint, $rootdir, $storage_cfg, $loopdevs, $snapname) = @_;
2021 my $volid = $mountpoint->{volume
};
2022 my $mount = $mountpoint->{mp
};
2024 return if !$volid || !$mount;
2028 if (defined($rootdir)) {
2029 $rootdir =~ s!/+$!!;
2030 $mount_path = "$rootdir/$mount";
2031 $mount_path =~ s!/+!/!g;
2032 File
::Path
::mkpath
($mount_path);
2035 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2037 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2041 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2042 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2044 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2045 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2047 if ($format eq 'subvol') {
2051 if ($scfg->{type
} eq 'zfspool') {
2052 my $path_arg = $path;
2053 $path_arg =~ s!^/+!!;
2054 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2056 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2059 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2064 } elsif ($format eq 'raw') {
2066 if ($scfg->{path
}) {
2067 $path = find_loopdev
($loopdevs, $path) if $loopdevs;
2068 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
2071 die "unsupported storage type '$scfg->{type}'\n";
2074 if ($isBase || defined($snapname)) {
2075 PVE
::Tools
::run_command
(['mount', '-o', 'ro', $path, $mount_path]);
2077 PVE
::Tools
::run_command
(['mount', $path, $mount_path]);
2082 die "unsupported image format '$format'\n";
2084 } elsif ($volid =~ m
|^/dev/.+|) {
2085 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2087 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2088 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2092 die "unsupported storage";
2095 sub get_vm_volumes
{
2096 my ($conf, $excludes) = @_;
2100 foreach_mountpoint
($conf, sub {
2101 my ($ms, $mountpoint) = @_;
2103 return if $excludes && $ms eq $excludes;
2105 my $volid = $mountpoint->{volume
};
2107 return if !$volid || $volid =~ m
|^/|;
2109 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2112 push @$vollist, $volid;
2121 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2125 my ($storage_cfg, $volid) = @_;
2127 if ($volid =~ m!^/dev/.+!) {
2132 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2134 die "cannot format volume '$volid' with no storage\n" if !$storage;
2136 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2138 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2139 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2141 die "cannot format volume '$volid' (format == $format)\n"
2142 if $format ne 'raw';
2148 my ($storecfg, $vollist) = @_;
2150 foreach my $volid (@$vollist) {
2151 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2157 my ($storecfg, $vmid, $settings, $conf) = @_;
2162 foreach_mountpoint
($settings, sub {
2163 my ($ms, $mountpoint) = @_;
2165 my $volid = $mountpoint->{volume
};
2166 my $mp = $mountpoint->{mp
};
2168 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2170 return if !$storage;
2172 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2173 my ($storeid, $size) = ($1, $2);
2175 $size = int($size*1024) * 1024;
2177 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2178 # fixme: use better naming ct-$vmid-disk-X.raw?
2180 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2182 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2184 format_disk
($storecfg, $volid);
2186 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2189 } elsif ($scfg->{type
} eq 'zfspool') {
2191 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2193 } elsif ($scfg->{type
} eq 'drbd') {
2195 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size);
2196 format_disk
($storecfg, $volid);
2198 } elsif ($scfg->{type
} eq 'rbd') {
2200 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2201 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size);
2202 format_disk
($storecfg, $volid);
2204 die "unable to create containers on storage type '$scfg->{type}'\n";
2206 push @$vollist, $volid;
2207 my $new_mountpoint = { volume
=> $volid, size
=> $size, mp
=> $mp };
2208 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2210 # use specified/existing volid
2214 # free allocated images on error
2216 destroy_disks
($storecfg, $vollist);
2222 # bash completion helper
2224 sub complete_os_templates
{
2225 my ($cmdname, $pname, $cvalue) = @_;
2227 my $cfg = PVE
::Storage
::config
();
2231 if ($cvalue =~ m/^([^:]+):/) {
2235 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2236 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2239 foreach my $id (keys %$data) {
2240 foreach my $item (@{$data->{$id}}) {
2241 push @$res, $item->{volid
} if defined($item->{volid
});
2248 sub complete_migration_target
{
2252 my $nodelist = PVE
::Cluster
::get_nodelist
();
2253 foreach my $node (@$nodelist) {
2254 next if $node eq $nodename;
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);