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 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1292 warn $@ if $@; # avoid errors - just warn
1295 my $safe_num_ne = sub {
1298 return 0 if !defined($a) && !defined($b);
1299 return 1 if !defined($a);
1300 return 1 if !defined($b);
1305 my $safe_string_ne = sub {
1308 return 0 if !defined($a) && !defined($b);
1309 return 1 if !defined($a);
1310 return 1 if !defined($b);
1316 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1318 if ($newnet->{type
} ne 'veth') {
1319 # for when there are physical interfaces
1320 die "cannot update interface of type $newnet->{type}";
1323 my $veth = "veth${vmid}i${netid}";
1324 my $eth = $newnet->{name
};
1326 if (my $oldnetcfg = $conf->{$opt}) {
1327 my $oldnet = parse_lxc_network
($oldnetcfg);
1329 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1330 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1332 PVE
::Network
::veth_delete
($veth);
1333 delete $conf->{$opt};
1334 write_config
($vmid, $conf);
1336 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1338 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1339 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1340 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1342 if ($oldnet->{bridge
}) {
1343 PVE
::Network
::tap_unplug
($veth);
1344 foreach (qw(bridge tag firewall)) {
1345 delete $oldnet->{$_};
1347 $conf->{$opt} = print_lxc_network
($oldnet);
1348 write_config
($vmid, $conf);
1351 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1352 foreach (qw(bridge tag firewall)) {
1353 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1355 $conf->{$opt} = print_lxc_network
($oldnet);
1356 write_config
($vmid, $conf);
1359 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1362 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1366 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1368 my $veth = "veth${vmid}i${netid}";
1369 my $vethpeer = $veth . "p";
1370 my $eth = $newnet->{name
};
1372 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1373 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1375 # attach peer in container
1376 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1377 PVE
::Tools
::run_command
($cmd);
1379 # link up peer in container
1380 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1381 PVE
::Tools
::run_command
($cmd);
1383 my $done = { type
=> 'veth' };
1384 foreach (qw(bridge tag firewall hwaddr name)) {
1385 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1387 $conf->{$opt} = print_lxc_network
($done);
1389 write_config
($vmid, $conf);
1392 sub update_ipconfig
{
1393 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1395 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1397 my $optdata = parse_lxc_network
($conf->{$opt});
1401 my $cmdargs = shift;
1402 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1404 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1406 my $change_ip_config = sub {
1407 my ($ipversion) = @_;
1409 my $family_opt = "-$ipversion";
1410 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1411 my $gw= "gw$suffix";
1412 my $ip= "ip$suffix";
1414 my $newip = $newnet->{$ip};
1415 my $newgw = $newnet->{$gw};
1416 my $oldip = $optdata->{$ip};
1418 my $change_ip = &$safe_string_ne($oldip, $newip);
1419 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1421 return if !$change_ip && !$change_gw;
1423 # step 1: add new IP, if this fails we cancel
1424 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1425 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1432 # step 2: replace gateway
1433 # If this fails we delete the added IP and cancel.
1434 # If it succeeds we save the config and delete the old IP, ignoring
1435 # errors. The config is then saved.
1436 # Note: 'ip route replace' can add
1439 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1442 # the route was not replaced, the old IP is still available
1443 # rollback (delete new IP) and cancel
1445 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1446 warn $@ if $@; # no need to die here
1451 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1452 # if the route was not deleted, the guest might have deleted it manually
1458 # from this point on we save the configuration
1459 # step 3: delete old IP ignoring errors
1460 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1461 # We need to enable promote_secondaries, otherwise our newly added
1462 # address will be removed along with the old one.
1465 if ($ipversion == 4) {
1466 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1467 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1468 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1470 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1472 warn $@ if $@; # no need to die here
1474 if ($ipversion == 4) {
1475 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1479 foreach my $property ($ip, $gw) {
1480 if ($newnet->{$property}) {
1481 $optdata->{$property} = $newnet->{$property};
1483 delete $optdata->{$property};
1486 $conf->{$opt} = print_lxc_network
($optdata);
1487 write_config
($vmid, $conf);
1488 $lxc_setup->setup_network($conf);
1491 &$change_ip_config(4);
1492 &$change_ip_config(6);
1496 # Internal snapshots
1498 # NOTE: Snapshot create/delete involves several non-atomic
1499 # action, and can take a long time.
1500 # So we try to avoid locking the file and use 'lock' variable
1501 # inside the config file instead.
1503 my $snapshot_copy_config = sub {
1504 my ($source, $dest) = @_;
1506 foreach my $k (keys %$source) {
1507 next if $k eq 'snapshots';
1508 next if $k eq 'snapstate';
1509 next if $k eq 'snaptime';
1510 next if $k eq 'vmstate';
1511 next if $k eq 'lock';
1512 next if $k eq 'digest';
1513 next if $k eq 'description';
1515 $dest->{$k} = $source->{$k};
1519 my $snapshot_prepare = sub {
1520 my ($vmid, $snapname, $comment) = @_;
1524 my $updatefn = sub {
1526 my $conf = load_config
($vmid);
1528 die "you can't take a snapshot if it's a template\n"
1529 if is_template
($conf);
1533 $conf->{lock} = 'snapshot';
1535 die "snapshot name '$snapname' already used\n"
1536 if defined($conf->{snapshots
}->{$snapname});
1538 my $storecfg = PVE
::Storage
::config
();
1539 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1541 $snap = $conf->{snapshots
}->{$snapname} = {};
1543 &$snapshot_copy_config($conf, $snap);
1545 $snap->{'snapstate'} = "prepare";
1546 $snap->{'snaptime'} = time();
1547 $snap->{'description'} = $comment if $comment;
1548 $conf->{snapshots
}->{$snapname} = $snap;
1550 write_config
($vmid, $conf);
1553 lock_container
($vmid, 10, $updatefn);
1558 my $snapshot_commit = sub {
1559 my ($vmid, $snapname) = @_;
1561 my $updatefn = sub {
1563 my $conf = load_config
($vmid);
1565 die "missing snapshot lock\n"
1566 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1568 die "snapshot '$snapname' does not exist\n"
1569 if !defined($conf->{snapshots
}->{$snapname});
1571 die "wrong snapshot state\n"
1572 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1573 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1575 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1576 delete $conf->{lock};
1577 $conf->{parent
} = $snapname;
1579 write_config
($vmid, $conf);
1582 lock_container
($vmid, 10 ,$updatefn);
1586 my ($feature, $conf, $storecfg, $snapname) = @_;
1590 foreach_mountpoint
($conf, sub {
1591 my ($ms, $mountpoint) = @_;
1593 return if $err; # skip further test
1595 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1597 # TODO: implement support for mountpoints
1598 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1602 return $err ?
0 : 1;
1605 sub snapshot_create
{
1606 my ($vmid, $snapname, $comment) = @_;
1608 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1610 my $conf = load_config
($vmid);
1612 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1613 my $running = check_running
($vmid);
1616 PVE
::Tools
::run_command
($cmd);
1619 my $storecfg = PVE
::Storage
::config
();
1620 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1621 my $volid = $rootinfo->{volume
};
1623 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1625 PVE
::Tools
::run_command
($cmd);
1628 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1629 &$snapshot_commit($vmid, $snapname);
1632 snapshot_delete
($vmid, $snapname, 1);
1637 sub snapshot_delete
{
1638 my ($vmid, $snapname, $force) = @_;
1644 my $updatefn = sub {
1646 $conf = load_config
($vmid);
1648 die "you can't delete a snapshot if vm is a template\n"
1649 if is_template
($conf);
1651 $snap = $conf->{snapshots
}->{$snapname};
1655 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1657 $snap->{snapstate
} = 'delete';
1659 write_config
($vmid, $conf);
1662 lock_container
($vmid, 10, $updatefn);
1664 my $storecfg = PVE
::Storage
::config
();
1666 my $del_snap = sub {
1670 if ($conf->{parent
} eq $snapname) {
1671 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1672 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1674 delete $conf->{parent
};
1678 delete $conf->{snapshots
}->{$snapname};
1680 write_config
($vmid, $conf);
1683 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1684 my $rootinfo = parse_ct_mountpoint
($rootfs);
1685 my $volid = $rootinfo->{volume
};
1688 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1692 if(!$err || ($err && $force)) {
1693 lock_container
($vmid, 10, $del_snap);
1695 die "Can't delete snapshot: $vmid $snapname $err\n";
1700 sub snapshot_rollback
{
1701 my ($vmid, $snapname) = @_;
1703 my $storecfg = PVE
::Storage
::config
();
1705 my $conf = load_config
($vmid);
1707 die "you can't rollback if vm is a template\n" if is_template
($conf);
1709 my $snap = $conf->{snapshots
}->{$snapname};
1711 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1713 my $rootfs = $snap->{rootfs
};
1714 my $rootinfo = parse_ct_mountpoint
($rootfs);
1715 my $volid = $rootinfo->{volume
};
1717 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1719 my $updatefn = sub {
1721 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1722 if $snap->{snapstate
};
1726 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1728 die "unable to rollback vm $vmid: vm is running\n"
1729 if check_running
($vmid);
1731 $conf->{lock} = 'rollback';
1735 # copy snapshot config to current config
1737 my $tmp_conf = $conf;
1738 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1739 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1740 delete $conf->{snaptime
};
1741 delete $conf->{snapname
};
1742 $conf->{parent
} = $snapname;
1744 write_config
($vmid, $conf);
1747 my $unlockfn = sub {
1748 delete $conf->{lock};
1749 write_config
($vmid, $conf);
1752 lock_container
($vmid, 10, $updatefn);
1754 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1756 lock_container
($vmid, 5, $unlockfn);
1759 sub template_create
{
1760 my ($vmid, $conf) = @_;
1762 my $storecfg = PVE
::Storage
::config
();
1764 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1765 my $volid = $rootinfo->{volume
};
1767 die "Template feature is not available for '$volid'\n"
1768 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1770 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1772 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1773 $rootinfo->{volume
} = $template_volid;
1774 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1776 write_config
($vmid, $conf);
1782 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1785 sub mountpoint_names
{
1788 my @names = ('rootfs');
1790 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1791 push @names, "mp$i";
1794 return $reverse ?
reverse @names : @names;
1797 sub foreach_mountpoint_full
{
1798 my ($conf, $reverse, $func) = @_;
1800 foreach my $key (mountpoint_names
($reverse)) {
1801 my $value = $conf->{$key};
1802 next if !defined($value);
1803 my $mountpoint = parse_ct_mountpoint
($value);
1804 $mountpoint->{mp
} = '/' if $key eq 'rootfs'; # just to be sure
1805 &$func($key, $mountpoint);
1809 sub foreach_mountpoint
{
1810 my ($conf, $func) = @_;
1812 foreach_mountpoint_full
($conf, 0, $func);
1815 sub foreach_mountpoint_reverse
{
1816 my ($conf, $func) = @_;
1818 foreach_mountpoint_full
($conf, 1, $func);
1821 sub check_ct_modify_config_perm
{
1822 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1824 return 1 if $authuser ne 'root@pam';
1826 foreach my $opt (@$key_list) {
1828 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1829 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1830 } elsif ($opt eq 'disk') {
1831 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1832 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1833 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1834 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1835 $opt eq 'searchdomain' || $opt eq 'hostname') {
1836 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1838 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1846 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1848 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1849 my $volid_list = get_vm_volumes
($conf);
1851 foreach_mountpoint_reverse
($conf, sub {
1852 my ($ms, $mountpoint) = @_;
1854 my $volid = $mountpoint->{volume
};
1855 my $mount = $mountpoint->{mp
};
1857 return if !$volid || !$mount;
1859 my $mount_path = "$rootdir/$mount";
1860 $mount_path =~ s!/+!/!g;
1862 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
1865 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
1878 my ($vmid, $storage_cfg, $conf, $mkdirs) = @_;
1880 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1882 my $volid_list = get_vm_volumes
($conf);
1883 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
1886 foreach_mountpoint
($conf, sub {
1887 my ($ms, $mountpoint) = @_;
1889 my $volid = $mountpoint->{volume
};
1890 my $mount = $mountpoint->{mp
};
1892 return if !$volid || !$mount;
1894 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
1895 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1896 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1898 die "unable to mount base volume - internal error" if $isBase;
1900 File
::Path
::make_path
"$rootdir/$mount" if $mkdirs;
1901 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
1905 warn "mounting container failed - $err";
1906 umount_all
($vmid, $storage_cfg, $conf, 1);
1913 sub mountpoint_mount_path
{
1914 my ($mountpoint, $storage_cfg, $snapname) = @_;
1916 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
1919 # use $rootdir = undef to just return the corresponding mount path
1920 sub mountpoint_mount
{
1921 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
1923 my $volid = $mountpoint->{volume
};
1924 my $mount = $mountpoint->{mp
};
1926 return if !$volid || !$mount;
1930 if (defined($rootdir)) {
1931 $rootdir =~ s!/+$!!;
1932 $mount_path = "$rootdir/$mount";
1933 $mount_path =~ s!/+!/!g;
1934 File
::Path
::mkpath
($mount_path);
1937 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1939 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
1943 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1944 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
1945 return $path if !$mount_path;
1947 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1948 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1950 if ($format eq 'subvol') {
1952 if ($scfg->{type
} eq 'zfspool') {
1953 my $path_arg = $path;
1954 $path_arg =~ s!^/+!!;
1955 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
1957 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
1960 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
1963 } elsif ($format eq 'raw') {
1965 if ($scfg->{path
}) {
1966 push @extra_opts, '-o', 'loop';
1967 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
1970 die "unsupported storage type '$scfg->{type}'\n";
1972 if ($isBase || defined($snapname)) {
1973 PVE
::Tools
::run_command
(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
1975 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
1979 die "unsupported image format '$format'\n";
1981 } elsif ($volid =~ m
|^/dev/.+|) {
1982 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
1984 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
1985 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
1989 die "unsupported storage";
1992 sub get_vm_volumes
{
1993 my ($conf, $excludes) = @_;
1997 foreach_mountpoint
($conf, sub {
1998 my ($ms, $mountpoint) = @_;
2000 return if $excludes && $ms eq $excludes;
2002 my $volid = $mountpoint->{volume
};
2004 return if !$volid || $volid =~ m
|^/|;
2006 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2009 push @$vollist, $volid;
2018 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2022 my ($storage_cfg, $volid) = @_;
2024 if ($volid =~ m!^/dev/.+!) {
2029 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2031 die "cannot format volume '$volid' with no storage\n" if !$storage;
2033 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2035 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2036 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2038 die "cannot format volume '$volid' (format == $format)\n"
2039 if $format ne 'raw';
2045 my ($storecfg, $vollist) = @_;
2047 foreach my $volid (@$vollist) {
2048 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2054 my ($storecfg, $vmid, $settings, $conf) = @_;
2059 foreach_mountpoint
($settings, sub {
2060 my ($ms, $mountpoint) = @_;
2062 my $volid = $mountpoint->{volume
};
2063 my $mp = $mountpoint->{mp
};
2065 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2067 return if !$storage;
2069 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2070 my ($storeid, $size) = ($1, $2);
2072 $size = int($size*1024) * 1024;
2074 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2075 # fixme: use better naming ct-$vmid-disk-X.raw?
2077 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2079 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2081 format_disk
($storecfg, $volid);
2083 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2086 } elsif ($scfg->{type
} eq 'zfspool') {
2088 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2090 } elsif ($scfg->{type
} eq 'drbd') {
2092 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size);
2093 format_disk
($storecfg, $volid);
2095 } elsif ($scfg->{type
} eq 'rbd') {
2097 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2098 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size);
2099 format_disk
($storecfg, $volid);
2101 die "unable to create containers on storage type '$scfg->{type}'\n";
2103 push @$vollist, $volid;
2104 my $new_mountpoint = { volume
=> $volid, size
=> $size, mp
=> $mp };
2105 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2107 # use specified/existing volid
2111 # free allocated images on error
2113 destroy_disks
($storecfg, $vollist);
2119 # bash completion helper
2121 sub complete_os_templates
{
2122 my ($cmdname, $pname, $cvalue) = @_;
2124 my $cfg = PVE
::Storage
::config
();
2128 if ($cvalue =~ m/^([^:]+):/) {
2132 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2133 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2136 foreach my $id (keys %$data) {
2137 foreach my $item (@{$data->{$id}}) {
2138 push @$res, $item->{volid
} if defined($item->{volid
});
2145 my $complete_ctid_full = sub {
2148 my $idlist = vmstatus
();
2150 my $active_hash = list_active_containers
();
2154 foreach my $id (keys %$idlist) {
2155 my $d = $idlist->{$id};
2156 if (defined($running)) {
2157 next if $d->{template
};
2158 next if $running && !$active_hash->{$id};
2159 next if !$running && $active_hash->{$id};
2168 return &$complete_ctid_full();
2171 sub complete_ctid_stopped
{
2172 return &$complete_ctid_full(0);
2175 sub complete_ctid_running
{
2176 return &$complete_ctid_full(1);