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
;
21 my $nodename = PVE
::INotify
::nodename
();
23 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
25 PVE
::JSONSchema
::register_format
('pve-lxc-network', \
&verify_lxc_network
);
26 sub verify_lxc_network
{
27 my ($value, $noerr) = @_;
29 return $value if parse_lxc_network
($value);
31 return undef if $noerr;
33 die "unable to parse network setting\n";
36 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', \
&verify_ct_mountpoint
);
37 sub verify_ct_mountpoint
{
38 my ($value, $noerr) = @_;
40 return $value if parse_ct_mountpoint
($value);
42 return undef if $noerr;
44 die "unable to parse CT mountpoint options\n";
47 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
48 type
=> 'string', format
=> 'pve-ct-mountpoint',
49 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
50 description
=> "Use volume as container root.",
54 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
55 description
=> "The name of the snapshot.",
56 type
=> 'string', format
=> 'pve-configid',
64 description
=> "Lock/unlock the VM.",
65 enum
=> [qw(migrate backup snapshot rollback)],
70 description
=> "Specifies whether a VM will be started during system bootup.",
73 startup
=> get_standard_option
('pve-startup-order'),
77 description
=> "Enable/disable Template.",
83 enum
=> ['amd64', 'i386'],
84 description
=> "OS architecture type.",
90 enum
=> ['debian', 'ubuntu', 'centos', 'archlinux'],
91 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
96 description
=> "Attach a console device (/dev/console) to the container.",
102 description
=> "Specify the number of tty available to the container",
110 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.",
118 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.",
126 description
=> "Amount of RAM for the VM in MB.",
133 description
=> "Amount of SWAP for the VM in MB.",
139 description
=> "Set a host name for the container.",
146 description
=> "Container description. Only used on the configuration web interface.",
151 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
156 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.",
158 rootfs
=> get_standard_option
('pve-ct-rootfs'),
161 type
=> 'string', format
=> 'pve-configid',
163 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
167 description
=> "Timestamp for snapshots.",
173 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).",
175 enum
=> ['shell', 'console', 'tty'],
180 my $valid_lxc_conf_keys = {
184 'lxc.haltsignal' => 1,
185 'lxc.rebootsignal' => 1,
186 'lxc.stopsignal' => 1,
188 'lxc.network.type' => 1,
189 'lxc.network.flags' => 1,
190 'lxc.network.link' => 1,
191 'lxc.network.mtu' => 1,
192 'lxc.network.name' => 1,
193 'lxc.network.hwaddr' => 1,
194 'lxc.network.ipv4' => 1,
195 'lxc.network.ipv4.gateway' => 1,
196 'lxc.network.ipv6' => 1,
197 'lxc.network.ipv6.gateway' => 1,
198 'lxc.network.script.up' => 1,
199 'lxc.network.script.down' => 1,
201 'lxc.console.logfile' => 1,
204 'lxc.devttydir' => 1,
205 'lxc.hook.autodev' => 1,
209 'lxc.mount.entry' => 1,
210 'lxc.mount.auto' => 1,
212 'lxc.rootfs.mount' => 1,
213 'lxc.rootfs.options' => 1,
217 'lxc.aa_profile' => 1,
218 'lxc.aa_allow_incomplete' => 1,
219 'lxc.se_context' => 1,
222 'lxc.hook.pre-start' => 1,
223 'lxc.hook.pre-mount' => 1,
224 'lxc.hook.mount' => 1,
225 'lxc.hook.start' => 1,
226 'lxc.hook.post-stop' => 1,
227 'lxc.hook.clone' => 1,
228 'lxc.hook.destroy' => 1,
231 'lxc.start.auto' => 1,
232 'lxc.start.delay' => 1,
233 'lxc.start.order' => 1,
235 'lxc.environment' => 1,
242 my $MAX_LXC_NETWORKS = 10;
243 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
244 $confdesc->{"net$i"} = {
246 type
=> 'string', format
=> 'pve-lxc-network',
247 description
=> "Specifies network interfaces for the container.\n\n".
248 "The string should have the follow format:\n\n".
249 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
250 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
251 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
252 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
256 my $MAX_MOUNT_POINTS = 10;
257 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
258 $confdesc->{"mp$i"} = {
260 type
=> 'string', format
=> 'pve-ct-mountpoint',
261 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
262 description
=> "Use volume as container mount point (experimental feature).",
267 sub write_pct_config
{
268 my ($filename, $conf) = @_;
270 delete $conf->{snapstate
}; # just to be sure
272 my $generate_raw_config = sub {
277 # add description as comment to top of file
278 my $descr = $conf->{description
} || '';
279 foreach my $cl (split(/\n/, $descr)) {
280 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
283 foreach my $key (sort keys %$conf) {
284 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
285 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
286 $raw .= "$key: $conf->{$key}\n";
289 if (my $lxcconf = $conf->{lxc
}) {
290 foreach my $entry (@$lxcconf) {
291 my ($k, $v) = @$entry;
299 my $raw = &$generate_raw_config($conf);
301 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
302 $raw .= "\n[$snapname]\n";
303 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
310 my ($key, $value) = @_;
312 die "unknown setting '$key'\n" if !$confdesc->{$key};
314 my $type = $confdesc->{$key}->{type
};
316 if (!defined($value)) {
317 die "got undefined value\n";
320 if ($value =~ m/[\n\r]/) {
321 die "property contains a line feed\n";
324 if ($type eq 'boolean') {
325 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
326 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
327 die "type check ('boolean') failed - got '$value'\n";
328 } elsif ($type eq 'integer') {
329 return int($1) if $value =~ m/^(\d+)$/;
330 die "type check ('integer') failed - got '$value'\n";
331 } elsif ($type eq 'number') {
332 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
333 die "type check ('number') failed - got '$value'\n";
334 } elsif ($type eq 'string') {
335 if (my $fmt = $confdesc->{$key}->{format
}) {
336 PVE
::JSONSchema
::check_format
($fmt, $value);
345 sub parse_pct_config
{
346 my ($filename, $raw) = @_;
348 return undef if !defined($raw);
351 digest
=> Digest
::SHA
::sha1_hex
($raw),
355 $filename =~ m
|/lxc/(\d
+).conf
$|
356 || die "got strange filename '$filename'";
364 my @lines = split(/\n/, $raw);
365 foreach my $line (@lines) {
366 next if $line =~ m/^\s*$/;
368 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
370 $conf->{description
} = $descr if $descr;
372 $conf = $res->{snapshots
}->{$section} = {};
376 if ($line =~ m/^\#(.*)\s*$/) {
377 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
381 if ($line =~ m/^(lxc\.[a-z0-9_\.]+)(:|\s*=)\s*(.*?)\s*$/) {
384 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
385 push @{$conf->{lxc
}}, [$key, $value];
387 warn "vm $vmid - unable to parse config: $line\n";
389 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
390 $descr .= PVE
::Tools
::decode_text
($2);
391 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
392 $conf->{snapstate
} = $1;
393 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
396 eval { $value = check_type
($key, $value); };
397 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
398 $conf->{$key} = $value;
400 warn "vm $vmid - unable to parse config: $line\n";
404 $conf->{description
} = $descr if $descr;
406 delete $res->{snapstate
}; # just to be sure
412 my $vmlist = PVE
::Cluster
::get_vmlist
();
414 return $res if !$vmlist || !$vmlist->{ids
};
415 my $ids = $vmlist->{ids
};
417 foreach my $vmid (keys %$ids) {
418 next if !$vmid; # skip CT0
419 my $d = $ids->{$vmid};
420 next if !$d->{node
} || $d->{node
} ne $nodename;
421 next if !$d->{type
} || $d->{type
} ne 'lxc';
422 $res->{$vmid}->{type
} = 'lxc';
427 sub cfs_config_path
{
428 my ($vmid, $node) = @_;
430 $node = $nodename if !$node;
431 return "nodes/$node/lxc/$vmid.conf";
435 my ($vmid, $node) = @_;
437 my $cfspath = cfs_config_path
($vmid, $node);
438 return "/etc/pve/$cfspath";
442 my ($vmid, $node) = @_;
444 $node = $nodename if !$node;
445 my $cfspath = cfs_config_path
($vmid, $node);
447 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
448 die "container $vmid does not exists\n" if !defined($conf);
454 my ($vmid, $conf) = @_;
456 my $dir = "/etc/pve/nodes/$nodename/lxc";
459 write_config
($vmid, $conf);
465 unlink config_file
($vmid, $nodename);
469 my ($vmid, $conf) = @_;
471 my $cfspath = cfs_config_path
($vmid);
473 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
476 # flock: we use one file handle per process, so lock file
477 # can be called multiple times and succeeds for the same process.
479 my $lock_handles = {};
480 my $lockdir = "/run/lock/lxc";
485 return "$lockdir/pve-config-${vmid}.lock";
489 my ($vmid, $timeout) = @_;
491 $timeout = 10 if !$timeout;
494 my $filename = lock_filename
($vmid);
496 mkdir $lockdir if !-d
$lockdir;
498 my $lock_func = sub {
499 if (!$lock_handles->{$$}->{$filename}) {
500 my $fh = new IO
::File
(">>$filename") ||
501 die "can't open file - $!\n";
502 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
505 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
506 print STDERR
"trying to aquire lock...";
509 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
510 # try again on EINTR (see bug #273)
511 if ($success || ($! != EINTR
)) {
516 print STDERR
" failed\n";
517 die "can't aquire lock - $!\n";
520 print STDERR
" OK\n";
523 $lock_handles->{$$}->{$filename}->{refcount
}++;
526 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
529 die "can't lock file '$filename' - $err";
536 my $filename = lock_filename
($vmid);
538 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
539 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
540 if ($refcount <= 0) {
541 $lock_handles->{$$}->{$filename} = undef;
548 my ($vmid, $timeout, $code, @param) = @_;
552 lock_aquire
($vmid, $timeout);
553 eval { $res = &$code(@param) };
565 return defined($confdesc->{$name});
568 # add JSON properties for create and set function
569 sub json_config_properties
{
572 foreach my $opt (keys %$confdesc) {
573 next if $opt eq 'parent' || $opt eq 'snaptime';
574 next if $prop->{$opt};
575 $prop->{$opt} = $confdesc->{$opt};
581 sub json_config_properties_no_rootfs
{
584 foreach my $opt (keys %$confdesc) {
585 next if $prop->{$opt};
586 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
587 $prop->{$opt} = $confdesc->{$opt};
593 # container status helpers
595 sub list_active_containers
{
597 my $filename = "/proc/net/unix";
599 # similar test is used by lcxcontainers.c: list_active_containers
602 my $fh = IO
::File-
>new ($filename, "r");
605 while (defined(my $line = <$fh>)) {
606 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
608 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
619 # warning: this is slow
623 my $active_hash = list_active_containers
();
625 return 1 if defined($active_hash->{$vmid});
630 sub get_container_disk_usage
{
633 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
643 if (my ($fsid, $total, $used, $avail) = $line =~
644 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
652 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
661 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
663 my $active_hash = list_active_containers
();
665 foreach my $vmid (keys %$list) {
666 my $d = $list->{$vmid};
668 my $running = defined($active_hash->{$vmid});
670 $d->{status
} = $running ?
'running' : 'stopped';
672 my $cfspath = cfs_config_path
($vmid);
673 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
675 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
676 $d->{name
} =~ s/[\s]//g;
678 $d->{cpus
} = $conf->{cpulimit
} // 0;
681 my $res = get_container_disk_usage
($vmid);
682 $d->{disk
} = $res->{used
};
683 $d->{maxdisk
} = $res->{total
};
686 # use 4GB by default ??
687 if (my $rootfs = $conf->{rootfs
}) {
688 my $rootinfo = parse_ct_mountpoint
($rootfs);
689 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
691 $d->{maxdisk
} = 4*1024*1024*1024;
697 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
698 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
709 $d->{template
} = is_template
($conf);
712 foreach my $vmid (keys %$list) {
713 my $d = $list->{$vmid};
714 next if $d->{status
} ne 'running';
716 $d->{uptime
} = 100; # fixme:
718 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
719 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
721 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
722 my @bytes = split(/\n/, $blkio_bytes);
723 foreach my $byte (@bytes) {
724 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
725 $d->{diskread
} = $2 if $key eq 'Read';
726 $d->{diskwrite
} = $2 if $key eq 'Write';
734 my $parse_size = sub {
737 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
738 my ($size, $unit) = ($1, $3);
741 $size = $size * 1024;
742 } elsif ($unit eq 'M') {
743 $size = $size * 1024 * 1024;
744 } elsif ($unit eq 'G') {
745 $size = $size * 1024 * 1024 * 1024;
751 sub parse_ct_mountpoint
{
758 foreach my $p (split (/,/, $data)) {
759 next if $p =~ m/^\s*$/;
761 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
762 my ($k, $v) = ($1, $2);
763 return undef if defined($res->{$k});
766 if (!$res->{volume
} && $p !~ m/=/) {
774 return undef if !defined($res->{volume
});
776 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
779 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
785 sub print_ct_mountpoint
{
786 my ($info, $nomp) = @_;
790 die "missing volume\n" if !$info->{volume
};
792 foreach my $o ('size', 'backup') {
793 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
795 $opts .= ",mp=$info->{mp}" if !$nomp;
797 return "$info->{volume}$opts";
800 sub print_lxc_network
{
803 die "no network name defined\n" if !$net->{name
};
805 my $res = "name=$net->{name}";
807 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
808 next if !defined($net->{$k});
809 $res .= ",$k=$net->{$k}";
815 sub parse_lxc_network
{
820 return $res if !$data;
822 foreach my $pv (split (/,/, $data)) {
823 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
830 $res->{type
} = 'veth';
831 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
836 sub read_cgroup_value
{
837 my ($group, $vmid, $name, $full) = @_;
839 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
841 return PVE
::Tools
::file_get_contents
($path) if $full;
843 return PVE
::Tools
::file_read_firstline
($path);
846 sub write_cgroup_value
{
847 my ($group, $vmid, $name, $value) = @_;
849 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
850 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
854 sub find_lxc_console_pids
{
858 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
861 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
864 my @args = split(/\0/, $cmdline);
866 # serach for lxc-console -n <vmid>
867 return if scalar(@args) != 3;
868 return if $args[1] ne '-n';
869 return if $args[2] !~ m/^\d+$/;
870 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
874 push @{$res->{$vmid}}, $pid;
886 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
888 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
890 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
895 my $ipv4_reverse_mask = [
931 # Note: we cannot use Net:IP, because that only allows strict
933 sub parse_ipv4_cidr
{
934 my ($cidr, $noerr) = @_;
936 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
937 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
940 return undef if $noerr;
942 die "unable to parse ipv4 address/mask\n";
948 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
951 sub update_lxc_config
{
952 my ($storage_cfg, $vmid, $conf) = @_;
954 my $dir = "/var/lib/lxc/$vmid";
956 if ($conf->{template
}) {
958 unlink "$dir/config";
965 die "missing 'arch' - internal error" if !$conf->{arch
};
966 $raw .= "lxc.arch = $conf->{arch}\n";
968 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
969 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
970 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
975 if (!has_dev_console
($conf)) {
976 $raw .= "lxc.console = none\n";
977 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
980 my $ttycount = get_tty_count
($conf);
981 $raw .= "lxc.tty = $ttycount\n";
983 my $utsname = $conf->{hostname
} || "CT$vmid";
984 $raw .= "lxc.utsname = $utsname\n";
986 my $memory = $conf->{memory
} || 512;
987 my $swap = $conf->{swap
} // 0;
989 my $lxcmem = int($memory*1024*1024);
990 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
992 my $lxcswap = int(($memory + $swap)*1024*1024);
993 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
995 if (my $cpulimit = $conf->{cpulimit
}) {
996 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
997 my $value = int(100000*$cpulimit);
998 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1001 my $shares = $conf->{cpuunits
} || 1024;
1002 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1004 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1005 $mountpoint->{mp
} = '/';
1006 my $volid = $mountpoint->{volume
};
1007 my $path = mountpoint_mount_path
($mountpoint, $storage_cfg);
1009 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1012 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1013 $path = "loop:$path" if $scfg->{path
};
1016 $raw .= "lxc.rootfs = $path\n";
1019 foreach my $k (keys %$conf) {
1020 next if $k !~ m/^net(\d+)$/;
1022 my $d = parse_lxc_network
($conf->{$k});
1024 $raw .= "lxc.network.type = veth\n";
1025 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1026 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1027 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1028 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1031 if (my $lxcconf = $conf->{lxc
}) {
1032 foreach my $entry (@$lxcconf) {
1033 my ($k, $v) = @$entry;
1034 $netcount++ if $k eq 'lxc.network.type';
1035 $raw .= "$k = $v\n";
1039 $raw .= "lxc.network.type = empty\n" if !$netcount;
1041 File
::Path
::mkpath
("$dir/rootfs");
1043 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1046 # verify and cleanup nameserver list (replace \0 with ' ')
1047 sub verify_nameserver_list
{
1048 my ($nameserver_list) = @_;
1051 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1052 PVE
::JSONSchema
::pve_verify_ip
($server);
1053 push @list, $server;
1056 return join(' ', @list);
1059 sub verify_searchdomain_list
{
1060 my ($searchdomain_list) = @_;
1063 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1064 # todo: should we add checks for valid dns domains?
1065 push @list, $server;
1068 return join(' ', @list);
1071 sub update_pct_config
{
1072 my ($vmid, $conf, $running, $param, $delete) = @_;
1080 my $pid = find_lxc_pid
($vmid);
1081 $rootdir = "/proc/$pid/root";
1084 if (defined($delete)) {
1085 foreach my $opt (@$delete) {
1086 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1087 die "unable to delete required option '$opt'\n";
1088 } elsif ($opt eq 'swap') {
1089 delete $conf->{$opt};
1090 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1091 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1092 delete $conf->{$opt};
1093 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1094 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1095 delete $conf->{$opt};
1096 push @nohotplug, $opt;
1098 } elsif ($opt =~ m/^net(\d)$/) {
1099 delete $conf->{$opt};
1102 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1103 } elsif ($opt =~ m/^mp(\d+)$/) {
1104 delete $conf->{$opt};
1105 push @nohotplug, $opt;
1107 } elsif ($opt eq 'rootfs') {
1112 write_config
($vmid, $conf) if $running;
1116 # There's no separate swap size to configure, there's memory and "total"
1117 # memory (iow. memory+swap). This means we have to change them together.
1118 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1119 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1120 if (defined($wanted_memory) || defined($wanted_swap)) {
1122 $wanted_memory //= ($conf->{memory
} || 512);
1123 $wanted_swap //= ($conf->{swap
} || 0);
1125 my $total = $wanted_memory + $wanted_swap;
1127 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1128 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1130 $conf->{memory
} = $wanted_memory;
1131 $conf->{swap
} = $wanted_swap;
1133 write_config
($vmid, $conf) if $running;
1136 foreach my $opt (keys %$param) {
1137 my $value = $param->{$opt};
1138 if ($opt eq 'hostname') {
1139 $conf->{$opt} = $value;
1140 } elsif ($opt eq 'onboot') {
1141 $conf->{$opt} = $value ?
1 : 0;
1142 } elsif ($opt eq 'startup') {
1143 $conf->{$opt} = $value;
1144 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1145 $conf->{$opt} = $value;
1146 push @nohotplug, $opt;
1148 } elsif ($opt eq 'nameserver') {
1149 my $list = verify_nameserver_list
($value);
1150 $conf->{$opt} = $list;
1151 push @nohotplug, $opt;
1153 } elsif ($opt eq 'searchdomain') {
1154 my $list = verify_searchdomain_list
($value);
1155 $conf->{$opt} = $list;
1156 push @nohotplug, $opt;
1158 } elsif ($opt eq 'cpulimit') {
1159 $conf->{$opt} = $value;
1160 push @nohotplug, $opt; # fixme: hotplug
1162 } elsif ($opt eq 'cpuunits') {
1163 $conf->{$opt} = $value;
1164 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1165 } elsif ($opt eq 'description') {
1166 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1167 } elsif ($opt =~ m/^net(\d+)$/) {
1169 my $net = parse_lxc_network
($value);
1171 $conf->{$opt} = print_lxc_network
($net);
1173 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1175 } elsif ($opt =~ m/^mp(\d+)$/) {
1176 $conf->{$opt} = $value;
1177 push @$new_disks, $opt;
1178 push @nohotplug, $opt;
1180 } elsif ($opt eq 'rootfs') {
1181 die "implement me: $opt";
1183 die "implement me: $opt";
1185 write_config
($vmid, $conf) if $running;
1188 if ($running && scalar(@nohotplug)) {
1189 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1193 my $storage_cfg = PVE
::Storage
::config
();
1194 PVE
::API2
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
1195 mount_all
($vmid, $storage_cfg, $conf, $new_disks, 1);
1196 umount_all
($vmid, $storage_cfg, $conf, 0);
1200 sub has_dev_console
{
1203 return !(defined($conf->{console
}) && !$conf->{console
});
1209 return $conf->{tty
} // $confdesc->{tty
}->{default};
1215 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1218 sub get_console_command
{
1219 my ($vmid, $conf) = @_;
1221 my $cmode = get_cmode
($conf);
1223 if ($cmode eq 'console') {
1224 return ['lxc-console', '-n', $vmid, '-t', 0];
1225 } elsif ($cmode eq 'tty') {
1226 return ['lxc-console', '-n', $vmid];
1227 } elsif ($cmode eq 'shell') {
1228 return ['lxc-attach', '--clear-env', '-n', $vmid];
1230 die "internal error";
1234 sub get_primary_ips
{
1237 # return data from net0
1239 return undef if !defined($conf->{net0
});
1240 my $net = parse_lxc_network
($conf->{net0
});
1242 my $ipv4 = $net->{ip
};
1244 if ($ipv4 =~ /^(dhcp|manual)$/) {
1250 my $ipv6 = $net->{ip6
};
1252 if ($ipv6 =~ /^(dhcp|manual)$/) {
1259 return ($ipv4, $ipv6);
1263 sub destroy_lxc_container
{
1264 my ($storage_cfg, $vmid, $conf) = @_;
1266 foreach_mountpoint
($conf, sub {
1267 my ($ms, $mountpoint) = @_;
1268 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $mountpoint->{volume
});
1269 PVE
::Storage
::vdisk_free
($storage_cfg, $mountpoint->{volume
}) if $vmid == $owner;
1272 rmdir "/var/lib/lxc/$vmid/rootfs";
1273 unlink "/var/lib/lxc/$vmid/config";
1274 rmdir "/var/lib/lxc/$vmid";
1275 destroy_config
($vmid);
1277 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1278 #PVE::Tools::run_command($cmd);
1281 sub vm_stop_cleanup
{
1282 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1287 my $vollist = get_vm_volumes
($conf);
1288 my $loopdevlist = get_vm_volumes
($conf, 'rootfs');
1290 detach_loops
($storage_cfg, $loopdevlist);
1291 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1294 warn $@ if $@; # avoid errors - just warn
1297 my $safe_num_ne = sub {
1300 return 0 if !defined($a) && !defined($b);
1301 return 1 if !defined($a);
1302 return 1 if !defined($b);
1307 my $safe_string_ne = sub {
1310 return 0 if !defined($a) && !defined($b);
1311 return 1 if !defined($a);
1312 return 1 if !defined($b);
1318 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1320 if ($newnet->{type
} ne 'veth') {
1321 # for when there are physical interfaces
1322 die "cannot update interface of type $newnet->{type}";
1325 my $veth = "veth${vmid}i${netid}";
1326 my $eth = $newnet->{name
};
1328 if (my $oldnetcfg = $conf->{$opt}) {
1329 my $oldnet = parse_lxc_network
($oldnetcfg);
1331 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1332 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1334 PVE
::Network
::veth_delete
($veth);
1335 delete $conf->{$opt};
1336 write_config
($vmid, $conf);
1338 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1340 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1341 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1342 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1344 if ($oldnet->{bridge
}) {
1345 PVE
::Network
::tap_unplug
($veth);
1346 foreach (qw(bridge tag firewall)) {
1347 delete $oldnet->{$_};
1349 $conf->{$opt} = print_lxc_network
($oldnet);
1350 write_config
($vmid, $conf);
1353 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1354 foreach (qw(bridge tag firewall)) {
1355 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1357 $conf->{$opt} = print_lxc_network
($oldnet);
1358 write_config
($vmid, $conf);
1361 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1364 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1368 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1370 my $veth = "veth${vmid}i${netid}";
1371 my $vethpeer = $veth . "p";
1372 my $eth = $newnet->{name
};
1374 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1375 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1377 # attach peer in container
1378 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1379 PVE
::Tools
::run_command
($cmd);
1381 # link up peer in container
1382 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1383 PVE
::Tools
::run_command
($cmd);
1385 my $done = { type
=> 'veth' };
1386 foreach (qw(bridge tag firewall hwaddr name)) {
1387 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1389 $conf->{$opt} = print_lxc_network
($done);
1391 write_config
($vmid, $conf);
1394 sub update_ipconfig
{
1395 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1397 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1399 my $optdata = parse_lxc_network
($conf->{$opt});
1403 my $cmdargs = shift;
1404 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1406 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1408 my $change_ip_config = sub {
1409 my ($ipversion) = @_;
1411 my $family_opt = "-$ipversion";
1412 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1413 my $gw= "gw$suffix";
1414 my $ip= "ip$suffix";
1416 my $newip = $newnet->{$ip};
1417 my $newgw = $newnet->{$gw};
1418 my $oldip = $optdata->{$ip};
1420 my $change_ip = &$safe_string_ne($oldip, $newip);
1421 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1423 return if !$change_ip && !$change_gw;
1425 # step 1: add new IP, if this fails we cancel
1426 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1427 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1434 # step 2: replace gateway
1435 # If this fails we delete the added IP and cancel.
1436 # If it succeeds we save the config and delete the old IP, ignoring
1437 # errors. The config is then saved.
1438 # Note: 'ip route replace' can add
1441 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1444 # the route was not replaced, the old IP is still available
1445 # rollback (delete new IP) and cancel
1447 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1448 warn $@ if $@; # no need to die here
1453 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1454 # if the route was not deleted, the guest might have deleted it manually
1460 # from this point on we save the configuration
1461 # step 3: delete old IP ignoring errors
1462 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1463 # We need to enable promote_secondaries, otherwise our newly added
1464 # address will be removed along with the old one.
1467 if ($ipversion == 4) {
1468 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1469 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1470 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1472 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1474 warn $@ if $@; # no need to die here
1476 if ($ipversion == 4) {
1477 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1481 foreach my $property ($ip, $gw) {
1482 if ($newnet->{$property}) {
1483 $optdata->{$property} = $newnet->{$property};
1485 delete $optdata->{$property};
1488 $conf->{$opt} = print_lxc_network
($optdata);
1489 write_config
($vmid, $conf);
1490 $lxc_setup->setup_network($conf);
1493 &$change_ip_config(4);
1494 &$change_ip_config(6);
1498 # Internal snapshots
1500 # NOTE: Snapshot create/delete involves several non-atomic
1501 # action, and can take a long time.
1502 # So we try to avoid locking the file and use 'lock' variable
1503 # inside the config file instead.
1505 my $snapshot_copy_config = sub {
1506 my ($source, $dest) = @_;
1508 foreach my $k (keys %$source) {
1509 next if $k eq 'snapshots';
1510 next if $k eq 'snapstate';
1511 next if $k eq 'snaptime';
1512 next if $k eq 'vmstate';
1513 next if $k eq 'lock';
1514 next if $k eq 'digest';
1515 next if $k eq 'description';
1517 $dest->{$k} = $source->{$k};
1521 my $snapshot_prepare = sub {
1522 my ($vmid, $snapname, $comment) = @_;
1526 my $updatefn = sub {
1528 my $conf = load_config
($vmid);
1530 die "you can't take a snapshot if it's a template\n"
1531 if is_template
($conf);
1535 $conf->{lock} = 'snapshot';
1537 die "snapshot name '$snapname' already used\n"
1538 if defined($conf->{snapshots
}->{$snapname});
1540 my $storecfg = PVE
::Storage
::config
();
1541 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1543 $snap = $conf->{snapshots
}->{$snapname} = {};
1545 &$snapshot_copy_config($conf, $snap);
1547 $snap->{'snapstate'} = "prepare";
1548 $snap->{'snaptime'} = time();
1549 $snap->{'description'} = $comment if $comment;
1550 $conf->{snapshots
}->{$snapname} = $snap;
1552 write_config
($vmid, $conf);
1555 lock_container
($vmid, 10, $updatefn);
1560 my $snapshot_commit = sub {
1561 my ($vmid, $snapname) = @_;
1563 my $updatefn = sub {
1565 my $conf = load_config
($vmid);
1567 die "missing snapshot lock\n"
1568 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1570 die "snapshot '$snapname' does not exist\n"
1571 if !defined($conf->{snapshots
}->{$snapname});
1573 die "wrong snapshot state\n"
1574 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1575 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1577 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1578 delete $conf->{lock};
1579 $conf->{parent
} = $snapname;
1581 write_config
($vmid, $conf);
1584 lock_container
($vmid, 10 ,$updatefn);
1588 my ($feature, $conf, $storecfg, $snapname) = @_;
1592 foreach_mountpoint
($conf, sub {
1593 my ($ms, $mountpoint) = @_;
1595 return if $err; # skip further test
1597 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1599 # TODO: implement support for mountpoints
1600 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1604 return $err ?
0 : 1;
1607 sub snapshot_create
{
1608 my ($vmid, $snapname, $comment) = @_;
1610 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1612 my $conf = load_config
($vmid);
1614 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1615 my $running = check_running
($vmid);
1618 PVE
::Tools
::run_command
($cmd);
1621 my $storecfg = PVE
::Storage
::config
();
1622 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1623 my $volid = $rootinfo->{volume
};
1625 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1627 PVE
::Tools
::run_command
($cmd);
1630 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1631 &$snapshot_commit($vmid, $snapname);
1634 snapshot_delete
($vmid, $snapname, 1);
1639 sub snapshot_delete
{
1640 my ($vmid, $snapname, $force) = @_;
1646 my $updatefn = sub {
1648 $conf = load_config
($vmid);
1650 die "you can't delete a snapshot if vm is a template\n"
1651 if is_template
($conf);
1653 $snap = $conf->{snapshots
}->{$snapname};
1657 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1659 $snap->{snapstate
} = 'delete';
1661 write_config
($vmid, $conf);
1664 lock_container
($vmid, 10, $updatefn);
1666 my $storecfg = PVE
::Storage
::config
();
1668 my $del_snap = sub {
1672 if ($conf->{parent
} eq $snapname) {
1673 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1674 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1676 delete $conf->{parent
};
1680 delete $conf->{snapshots
}->{$snapname};
1682 write_config
($vmid, $conf);
1685 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1686 my $rootinfo = parse_ct_mountpoint
($rootfs);
1687 my $volid = $rootinfo->{volume
};
1690 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1694 if(!$err || ($err && $force)) {
1695 lock_container
($vmid, 10, $del_snap);
1697 die "Can't delete snapshot: $vmid $snapname $err\n";
1702 sub snapshot_rollback
{
1703 my ($vmid, $snapname) = @_;
1705 my $storecfg = PVE
::Storage
::config
();
1707 my $conf = load_config
($vmid);
1709 die "you can't rollback if vm is a template\n" if is_template
($conf);
1711 my $snap = $conf->{snapshots
}->{$snapname};
1713 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1715 my $rootfs = $snap->{rootfs
};
1716 my $rootinfo = parse_ct_mountpoint
($rootfs);
1717 my $volid = $rootinfo->{volume
};
1719 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1721 my $updatefn = sub {
1723 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1724 if $snap->{snapstate
};
1728 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1730 die "unable to rollback vm $vmid: vm is running\n"
1731 if check_running
($vmid);
1733 $conf->{lock} = 'rollback';
1737 # copy snapshot config to current config
1739 my $tmp_conf = $conf;
1740 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1741 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1742 delete $conf->{snaptime
};
1743 delete $conf->{snapname
};
1744 $conf->{parent
} = $snapname;
1746 write_config
($vmid, $conf);
1749 my $unlockfn = sub {
1750 delete $conf->{lock};
1751 write_config
($vmid, $conf);
1754 lock_container
($vmid, 10, $updatefn);
1756 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1758 lock_container
($vmid, 5, $unlockfn);
1761 sub template_create
{
1762 my ($vmid, $conf) = @_;
1764 my $storecfg = PVE
::Storage
::config
();
1766 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1767 my $volid = $rootinfo->{volume
};
1769 die "Template feature is not available for '$volid'\n"
1770 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1772 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1774 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1775 $rootinfo->{volume
} = $template_volid;
1776 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1778 write_config
($vmid, $conf);
1784 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1787 sub mountpoint_names
{
1790 my @names = ('rootfs');
1792 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1793 push @names, "mp$i";
1796 return $reverse ?
reverse @names : @names;
1799 sub foreach_mountpoint_full
{
1800 my ($conf, $reverse, $func) = @_;
1802 foreach my $key (mountpoint_names
($reverse)) {
1803 my $value = $conf->{$key};
1804 next if !defined($value);
1805 my $mountpoint = parse_ct_mountpoint
($value);
1806 $mountpoint->{mp
} = '/' if $key eq 'rootfs'; # just to be sure
1807 &$func($key, $mountpoint);
1811 sub foreach_mountpoint
{
1812 my ($conf, $func) = @_;
1814 foreach_mountpoint_full
($conf, 0, $func);
1817 sub foreach_mountpoint_reverse
{
1818 my ($conf, $func) = @_;
1820 foreach_mountpoint_full
($conf, 1, $func);
1823 sub loopdevices_list
{
1828 if ($line =~ m/^(\/dev\
/loop\d+)\s+\d\s+\d\s+\d\s+\d\s(\S+)$/) {
1829 $loopdev->{$1} = $2;
1833 PVE
::Tools
::run_command
(['losetup'], outfunc
=> $parser);
1838 sub blockdevices_list
{
1841 dir_glob_foreach
("/sys/dev/block/", '(\d+):(\d+)', sub {
1842 my (undef, $major, $minor) = @_;
1843 my $bdev = readlink("/sys/dev/block/$major:$minor");
1844 $bdev =~ s/\.\.\/\.\.\/devices\
/virtual\/block\
//\
/dev\//;
1845 $bdevs->{$bdev}->{major
} = $major;
1846 $bdevs->{$bdev}->{minor
} = $minor;
1852 my ($loopdevs, $path) = @_;
1854 foreach my $dev (keys %$loopdevs){
1855 return $dev if $loopdevs->{$dev} eq $path;
1859 sub check_ct_modify_config_perm
{
1860 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1862 return 1 if $authuser ne 'root@pam';
1864 foreach my $opt (@$key_list) {
1866 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1867 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1868 } elsif ($opt eq 'disk') {
1869 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1870 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1871 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1872 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1873 $opt eq 'searchdomain' || $opt eq 'hostname') {
1874 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1876 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1884 my ($storage_cfg, $vollist, $snapname) = @_;
1888 foreach my $volid (@$vollist) {
1890 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1891 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1893 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1894 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1896 if ($format eq 'raw' && $scfg->{path
}) {
1897 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
1902 $loopdev = $line if $line =~m
|^/dev/loop\d
+$|;
1903 $loopdevs->{$loopdev} = $path;
1906 PVE
::Tools
::run_command
(['losetup', '--find', '--show', $path], outfunc
=> $parser);
1914 my ($storage_cfg, $vollist, $snapname) = @_;
1916 my $loopdevs = loopdevices_list
();
1918 foreach my $volid (@$vollist) {
1920 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1921 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1923 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1924 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1926 if ($format eq 'raw' && $scfg->{path
}) {
1927 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
1928 foreach my $dev (keys %$loopdevs){
1929 PVE
::Tools
::run_command
(['losetup', '-d', $dev]) if $loopdevs->{$dev} eq $path;
1936 my ($vmid, $storage_cfg, $conf, $noerr, $loopdevs) = @_;
1938 $loopdevs ||= loopdevices_list
();
1940 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1941 my $volid_list = get_vm_volumes
($conf);
1943 foreach_mountpoint_reverse
($conf, sub {
1944 my ($ms, $mountpoint) = @_;
1946 my $volid = $mountpoint->{volume
};
1947 my $mount = $mountpoint->{mp
};
1949 return if !$volid || !$mount;
1951 my $mount_path = "$rootdir/$mount";
1952 $mount_path =~ s!/+!/!g;
1954 # fixme: test if mounted?
1956 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
1967 PVE
::LXC
::detach_loops
($storage_cfg, $volid_list);
1971 my ($vmid, $storage_cfg, $conf, $mkdirs) = @_;
1973 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1975 my $volid_list = get_vm_volumes
($conf);
1976 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
1980 $loopdevs = attach_loops
($storage_cfg, $volid_list);
1982 foreach_mountpoint
($conf, sub {
1983 my ($ms, $mountpoint) = @_;
1985 my $volid = $mountpoint->{volume
};
1986 my $mount = $mountpoint->{mp
};
1988 return if !$volid || !$mount;
1990 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
1991 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1992 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1994 die "unable to mount base volume - internal error" if $isBase;
1996 File
::Path
::make_path
"$rootdir/$mount" if $mkdirs;
1997 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg, $loopdevs);
2001 warn "mounting container failed - $err";
2002 umount_all
($vmid, $storage_cfg, $conf, 1);
2005 return wantarray ?
($rootdir, $loopdevs) : $rootdir;
2009 sub mountpoint_mount_path
{
2010 my ($mountpoint, $storage_cfg, $loopdevs, $snapname) = @_;
2012 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $loopdevs, $snapname);
2015 # use $rootdir = undef to just return the corresponding mount path
2016 sub mountpoint_mount
{
2017 my ($mountpoint, $rootdir, $storage_cfg, $loopdevs, $snapname) = @_;
2019 my $volid = $mountpoint->{volume
};
2020 my $mount = $mountpoint->{mp
};
2022 return if !$volid || !$mount;
2026 if (defined($rootdir)) {
2027 $rootdir =~ s!/+$!!;
2028 $mount_path = "$rootdir/$mount";
2029 $mount_path =~ s!/+!/!g;
2030 File
::Path
::mkpath
($mount_path);
2033 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2035 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2039 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2040 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2042 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2043 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2045 if ($format eq 'subvol') {
2049 if ($scfg->{type
} eq 'zfspool') {
2050 my $path_arg = $path;
2051 $path_arg =~ s!^/+!!;
2052 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2054 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2057 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2062 } elsif ($format eq 'raw') {
2064 if ($scfg->{path
}) {
2065 $path = find_loopdev
($loopdevs, $path) if $loopdevs;
2066 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
2069 die "unsupported storage type '$scfg->{type}'\n";
2072 if ($isBase || defined($snapname)) {
2073 PVE
::Tools
::run_command
(['mount', '-o', 'ro', $path, $mount_path]);
2075 PVE
::Tools
::run_command
(['mount', $path, $mount_path]);
2080 die "unsupported image format '$format'\n";
2082 } elsif ($volid =~ m
|^/dev/.+|) {
2083 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2085 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2086 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2090 die "unsupported storage";
2093 sub get_vm_volumes
{
2094 my ($conf, $excludes) = @_;
2098 foreach_mountpoint
($conf, sub {
2099 my ($ms, $mountpoint) = @_;
2101 return if $excludes && $ms eq $excludes;
2103 my $volid = $mountpoint->{volume
};
2105 return if !$volid || $volid =~ m
|^/|;
2107 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2110 push @$vollist, $volid;
2116 # bash completion helper
2118 sub complete_os_templates
{
2119 my ($cmdname, $pname, $cvalue) = @_;
2121 my $cfg = PVE
::Storage
::config
();
2125 if ($cvalue =~ m/^([^:]+):/) {
2129 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2130 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2133 foreach my $id (keys %$data) {
2134 foreach my $item (@{$data->{$id}}) {
2135 push @$res, $item->{volid
} if defined($item->{volid
});
2142 sub complete_migration_target
{
2146 my $nodelist = PVE
::Cluster
::get_nodelist
();
2147 foreach my $node (@$nodelist) {
2148 next if $node eq $nodename;
2155 my $complete_ctid_full = sub {
2158 my $idlist = vmstatus
();
2160 my $active_hash = list_active_containers
();
2164 foreach my $id (keys %$idlist) {
2165 my $d = $idlist->{$id};
2166 if (defined($running)) {
2167 next if $d->{template
};
2168 next if $running && !$active_hash->{$id};
2169 next if !$running && $active_hash->{$id};
2178 return &$complete_ctid_full();
2181 sub complete_ctid_stopped
{
2182 return &$complete_ctid_full(0);
2185 sub complete_ctid_running
{
2186 return &$complete_ctid_full(1);