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'],
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";
444 my $cfspath = cfs_config_path
($vmid);
446 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
447 die "container $vmid does not exists\n" if !defined($conf);
453 my ($vmid, $conf) = @_;
455 my $dir = "/etc/pve/nodes/$nodename/lxc";
458 write_config
($vmid, $conf);
464 unlink config_file
($vmid, $nodename);
468 my ($vmid, $conf) = @_;
470 my $cfspath = cfs_config_path
($vmid);
472 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
475 # flock: we use one file handle per process, so lock file
476 # can be called multiple times and succeeds for the same process.
478 my $lock_handles = {};
479 my $lockdir = "/run/lock/lxc";
484 return "$lockdir/pve-config-{$vmid}.lock";
488 my ($vmid, $timeout) = @_;
490 $timeout = 10 if !$timeout;
493 my $filename = lock_filename
($vmid);
495 mkdir $lockdir if !-d
$lockdir;
497 my $lock_func = sub {
498 if (!$lock_handles->{$$}->{$filename}) {
499 my $fh = new IO
::File
(">>$filename") ||
500 die "can't open file - $!\n";
501 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
504 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
505 print STDERR
"trying to aquire lock...";
508 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
509 # try again on EINTR (see bug #273)
510 if ($success || ($! != EINTR
)) {
515 print STDERR
" failed\n";
516 die "can't aquire lock - $!\n";
519 $lock_handles->{$$}->{$filename}->{refcount
}++;
521 print STDERR
" OK\n";
525 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
528 die "can't lock file '$filename' - $err";
535 my $filename = lock_filename
($vmid);
537 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
538 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
539 if ($refcount <= 0) {
540 $lock_handles->{$$}->{$filename} = undef;
547 my ($vmid, $timeout, $code, @param) = @_;
551 lock_aquire
($vmid, $timeout);
552 eval { $res = &$code(@param) };
564 return defined($confdesc->{$name});
567 # add JSON properties for create and set function
568 sub json_config_properties
{
571 foreach my $opt (keys %$confdesc) {
572 next if $opt eq 'parent' || $opt eq 'snaptime';
573 next if $prop->{$opt};
574 $prop->{$opt} = $confdesc->{$opt};
580 sub json_config_properties_no_rootfs
{
583 foreach my $opt (keys %$confdesc) {
584 next if $prop->{$opt};
585 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
586 $prop->{$opt} = $confdesc->{$opt};
592 # container status helpers
594 sub list_active_containers
{
596 my $filename = "/proc/net/unix";
598 # similar test is used by lcxcontainers.c: list_active_containers
601 my $fh = IO
::File-
>new ($filename, "r");
604 while (defined(my $line = <$fh>)) {
605 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
607 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
618 # warning: this is slow
622 my $active_hash = list_active_containers
();
624 return 1 if defined($active_hash->{$vmid});
629 sub get_container_disk_usage
{
632 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
642 if (my ($fsid, $total, $used, $avail) = $line =~
643 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
651 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
660 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
662 my $active_hash = list_active_containers
();
664 foreach my $vmid (keys %$list) {
665 my $d = $list->{$vmid};
667 my $running = defined($active_hash->{$vmid});
669 $d->{status
} = $running ?
'running' : 'stopped';
671 my $cfspath = cfs_config_path
($vmid);
672 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
674 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
675 $d->{name
} =~ s/[\s]//g;
677 $d->{cpus
} = $conf->{cpulimit
} // 0;
680 my $res = get_container_disk_usage
($vmid);
681 $d->{disk
} = $res->{used
};
682 $d->{maxdisk
} = $res->{total
};
685 # use 4GB by default ??
686 if (my $rootfs = $conf->{rootfs
}) {
687 my $rootinfo = parse_ct_mountpoint
($rootfs);
688 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
690 $d->{maxdisk
} = 4*1024*1024*1024;
696 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
697 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
708 $d->{template
} = is_template
($conf);
711 foreach my $vmid (keys %$list) {
712 my $d = $list->{$vmid};
713 next if $d->{status
} ne 'running';
715 $d->{uptime
} = 100; # fixme:
717 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
718 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
720 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
721 my @bytes = split(/\n/, $blkio_bytes);
722 foreach my $byte (@bytes) {
723 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
724 $d->{diskread
} = $2 if $key eq 'Read';
725 $d->{diskwrite
} = $2 if $key eq 'Write';
733 my $parse_size = sub {
736 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
737 my ($size, $unit) = ($1, $3);
740 $size = $size * 1024;
741 } elsif ($unit eq 'M') {
742 $size = $size * 1024 * 1024;
743 } elsif ($unit eq 'G') {
744 $size = $size * 1024 * 1024 * 1024;
750 sub parse_ct_mountpoint
{
757 foreach my $p (split (/,/, $data)) {
758 next if $p =~ m/^\s*$/;
760 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
761 my ($k, $v) = ($1, $2);
762 return undef if defined($res->{$k});
765 if (!$res->{volume
} && $p !~ m/=/) {
773 return undef if !$res->{volume
};
775 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
778 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
784 sub print_ct_mountpoint
{
789 die "missing volume\n" if !$info->{volume
};
791 foreach my $o ('size', 'backup') {
792 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
795 return "$info->{volume}$opts";
798 sub print_lxc_network
{
801 die "no network name defined\n" if !$net->{name
};
803 my $res = "name=$net->{name}";
805 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
806 next if !defined($net->{$k});
807 $res .= ",$k=$net->{$k}";
813 sub parse_lxc_network
{
818 return $res if !$data;
820 foreach my $pv (split (/,/, $data)) {
821 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
828 $res->{type
} = 'veth';
829 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
834 sub read_cgroup_value
{
835 my ($group, $vmid, $name, $full) = @_;
837 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
839 return PVE
::Tools
::file_get_contents
($path) if $full;
841 return PVE
::Tools
::file_read_firstline
($path);
844 sub write_cgroup_value
{
845 my ($group, $vmid, $name, $value) = @_;
847 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
848 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
852 sub find_lxc_console_pids
{
856 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
859 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
862 my @args = split(/\0/, $cmdline);
864 # serach for lxc-console -n <vmid>
865 return if scalar(@args) != 3;
866 return if $args[1] ne '-n';
867 return if $args[2] !~ m/^\d+$/;
868 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
872 push @{$res->{$vmid}}, $pid;
884 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
886 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
888 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
893 my $ipv4_reverse_mask = [
929 # Note: we cannot use Net:IP, because that only allows strict
931 sub parse_ipv4_cidr
{
932 my ($cidr, $noerr) = @_;
934 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
935 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
938 return undef if $noerr;
940 die "unable to parse ipv4 address/mask\n";
946 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
949 sub update_lxc_config
{
950 my ($storage_cfg, $vmid, $conf) = @_;
952 my $dir = "/var/lib/lxc/$vmid";
954 if ($conf->{template
}) {
956 unlink "$dir/config";
963 die "missing 'arch' - internal error" if !$conf->{arch
};
964 $raw .= "lxc.arch = $conf->{arch}\n";
966 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
967 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
968 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
973 if (!has_dev_console
($conf)) {
974 $raw .= "lxc.console = none\n";
975 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
978 my $ttycount = get_tty_count
($conf);
979 $raw .= "lxc.tty = $ttycount\n";
981 my $utsname = $conf->{hostname
} || "CT$vmid";
982 $raw .= "lxc.utsname = $utsname\n";
984 my $memory = $conf->{memory
} || 512;
985 my $swap = $conf->{swap
} // 0;
987 my $lxcmem = int($memory*1024*1024);
988 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
990 my $lxcswap = int(($memory + $swap)*1024*1024);
991 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
993 if (my $cpulimit = $conf->{cpulimit
}) {
994 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
995 my $value = int(100000*$cpulimit);
996 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
999 my $shares = $conf->{cpuunits
} || 1024;
1000 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1002 PVE
::LXC
::foreach_mountpoint
($conf, sub {
1003 my ($ms, $mountpoint) = @_;
1005 my $volid = $mountpoint->{volume
};
1006 return if !$volid || $volid =~ m
|^/dev/.+|;
1008 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1010 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1011 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
1013 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1014 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1016 die "unable to use template as mountpoint\n" if $isBase;
1018 if ($format eq 'subvol') {
1019 $mountpoint->{mp
} =~ s/^\///s
;
1020 if ($ms eq 'rootfs') {
1021 $raw .= "lxc.rootfs = $path\n";
1023 $raw .= "lxc.mount.entry = $path $mountpoint->{mp} none defaults,bind 0 0\n";
1025 } elsif ($format eq 'raw') {
1027 if ($scfg->{path
}) {
1028 $raw .= "lxc.rootfs = loop:$path\n" if $ms eq 'rootfs';
1029 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
1030 $raw .= "lxc.rootfs = $path\n" if $ms eq 'rootfs';
1032 die "unsupported storage type '$scfg->{type}'\n";
1035 die "unsupported image format '$format'\n";
1041 foreach my $k (keys %$conf) {
1042 next if $k !~ m/^net(\d+)$/;
1044 my $d = parse_lxc_network
($conf->{$k});
1046 $raw .= "lxc.network.type = veth\n";
1047 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1048 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1049 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1050 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1053 if (my $lxcconf = $conf->{lxc
}) {
1054 foreach my $entry (@$lxcconf) {
1055 my ($k, $v) = @$entry;
1056 $netcount++ if $k eq 'lxc.network.type';
1057 $raw .= "$k = $v\n";
1061 $raw .= "lxc.network.type = empty\n" if !$netcount;
1063 File
::Path
::mkpath
("$dir/rootfs");
1065 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1068 # verify and cleanup nameserver list (replace \0 with ' ')
1069 sub verify_nameserver_list
{
1070 my ($nameserver_list) = @_;
1073 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1074 PVE
::JSONSchema
::pve_verify_ip
($server);
1075 push @list, $server;
1078 return join(' ', @list);
1081 sub verify_searchdomain_list
{
1082 my ($searchdomain_list) = @_;
1085 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1086 # todo: should we add checks for valid dns domains?
1087 push @list, $server;
1090 return join(' ', @list);
1093 sub update_pct_config
{
1094 my ($vmid, $conf, $running, $param, $delete) = @_;
1100 my $pid = find_lxc_pid
($vmid);
1101 $rootdir = "/proc/$pid/root";
1104 if (defined($delete)) {
1105 foreach my $opt (@$delete) {
1106 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1107 die "unable to delete required option '$opt'\n";
1108 } elsif ($opt eq 'swap') {
1109 delete $conf->{$opt};
1110 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1111 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1112 delete $conf->{$opt};
1113 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1114 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1115 delete $conf->{$opt};
1116 push @nohotplug, $opt;
1118 } elsif ($opt =~ m/^net(\d)$/) {
1119 delete $conf->{$opt};
1122 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1126 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1130 # There's no separate swap size to configure, there's memory and "total"
1131 # memory (iow. memory+swap). This means we have to change them together.
1132 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1133 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1134 if (defined($wanted_memory) || defined($wanted_swap)) {
1136 $wanted_memory //= ($conf->{memory
} || 512);
1137 $wanted_swap //= ($conf->{swap
} || 0);
1139 my $total = $wanted_memory + $wanted_swap;
1141 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1142 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1144 $conf->{memory
} = $wanted_memory;
1145 $conf->{swap
} = $wanted_swap;
1147 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1150 foreach my $opt (keys %$param) {
1151 my $value = $param->{$opt};
1152 if ($opt eq 'hostname') {
1153 $conf->{$opt} = $value;
1154 } elsif ($opt eq 'onboot') {
1155 $conf->{$opt} = $value ?
1 : 0;
1156 } elsif ($opt eq 'startup') {
1157 $conf->{$opt} = $value;
1158 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1159 $conf->{$opt} = $value;
1160 push @nohotplug, $opt;
1162 } elsif ($opt eq 'nameserver') {
1163 my $list = verify_nameserver_list
($value);
1164 $conf->{$opt} = $list;
1165 push @nohotplug, $opt;
1167 } elsif ($opt eq 'searchdomain') {
1168 my $list = verify_searchdomain_list
($value);
1169 $conf->{$opt} = $list;
1170 push @nohotplug, $opt;
1172 } elsif ($opt eq 'cpulimit') {
1173 $conf->{$opt} = $value;
1174 push @nohotplug, $opt; # fixme: hotplug
1176 } elsif ($opt eq 'cpuunits') {
1177 $conf->{$opt} = $value;
1178 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1179 } elsif ($opt eq 'description') {
1180 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1181 } elsif ($opt =~ m/^net(\d+)$/) {
1183 my $net = parse_lxc_network
($value);
1185 $conf->{$opt} = print_lxc_network
($net);
1187 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1190 die "implement me: $opt";
1192 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1195 if ($running && scalar(@nohotplug)) {
1196 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
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 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1267 if (defined($rootinfo->{volume
})) {
1268 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $rootinfo->{volume
});
1269 PVE
::Storage
::vdisk_free
($storage_cfg, $rootinfo->{volume
}) if $vmid == $owner;;
1271 rmdir "/var/lib/lxc/$vmid/rootfs";
1272 unlink "/var/lib/lxc/$vmid/config";
1273 rmdir "/var/lib/lxc/$vmid";
1274 destroy_config
($vmid);
1276 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1277 #PVE::Tools::run_command($cmd);
1280 sub vm_stop_cleanup
{
1281 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1286 my $loopdevs = loopdevices_list
();
1288 PVE
::LXC
::foreach_mountpoint
($conf, sub {
1289 my ($ms, $mountpoint) = @_;
1291 my $volid = $mountpoint->{volume
};
1292 #detach loopdevices of non rootfs mountpoints
1293 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1294 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1295 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1296 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1298 if($ms ne 'rootfs' && $format eq 'raw' && ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs')) {
1299 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
1300 foreach my $dev (keys %$loopdevs){
1301 PVE
::Tools
::run_command
(['losetup', '-d', $dev]) if $loopdevs->{$dev} eq $path;
1305 PVE
::Storage
::deactivate_volumes
($storage_cfg, [$volid]);
1310 warn $@ if $@; # avoid errors - just warn
1313 my $safe_num_ne = sub {
1316 return 0 if !defined($a) && !defined($b);
1317 return 1 if !defined($a);
1318 return 1 if !defined($b);
1323 my $safe_string_ne = sub {
1326 return 0 if !defined($a) && !defined($b);
1327 return 1 if !defined($a);
1328 return 1 if !defined($b);
1334 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1336 if ($newnet->{type
} ne 'veth') {
1337 # for when there are physical interfaces
1338 die "cannot update interface of type $newnet->{type}";
1341 my $veth = "veth${vmid}i${netid}";
1342 my $eth = $newnet->{name
};
1344 if (my $oldnetcfg = $conf->{$opt}) {
1345 my $oldnet = parse_lxc_network
($oldnetcfg);
1347 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1348 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1350 PVE
::Network
::veth_delete
($veth);
1351 delete $conf->{$opt};
1352 PVE
::LXC
::write_config
($vmid, $conf);
1354 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1356 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1357 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1358 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1360 if ($oldnet->{bridge
}) {
1361 PVE
::Network
::tap_unplug
($veth);
1362 foreach (qw(bridge tag firewall)) {
1363 delete $oldnet->{$_};
1365 $conf->{$opt} = print_lxc_network
($oldnet);
1366 PVE
::LXC
::write_config
($vmid, $conf);
1369 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1370 foreach (qw(bridge tag firewall)) {
1371 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1373 $conf->{$opt} = print_lxc_network
($oldnet);
1374 PVE
::LXC
::write_config
($vmid, $conf);
1377 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1380 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1384 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1386 my $veth = "veth${vmid}i${netid}";
1387 my $vethpeer = $veth . "p";
1388 my $eth = $newnet->{name
};
1390 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1391 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1393 # attach peer in container
1394 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1395 PVE
::Tools
::run_command
($cmd);
1397 # link up peer in container
1398 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1399 PVE
::Tools
::run_command
($cmd);
1401 my $done = { type
=> 'veth' };
1402 foreach (qw(bridge tag firewall hwaddr name)) {
1403 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1405 $conf->{$opt} = print_lxc_network
($done);
1407 PVE
::LXC
::write_config
($vmid, $conf);
1410 sub update_ipconfig
{
1411 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1413 my $lxc_setup = PVE
::LXCSetup-
>new($conf, $rootdir);
1415 my $optdata = parse_lxc_network
($conf->{$opt});
1419 my $cmdargs = shift;
1420 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1422 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1424 my $change_ip_config = sub {
1425 my ($ipversion) = @_;
1427 my $family_opt = "-$ipversion";
1428 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1429 my $gw= "gw$suffix";
1430 my $ip= "ip$suffix";
1432 my $newip = $newnet->{$ip};
1433 my $newgw = $newnet->{$gw};
1434 my $oldip = $optdata->{$ip};
1436 my $change_ip = &$safe_string_ne($oldip, $newip);
1437 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1439 return if !$change_ip && !$change_gw;
1441 # step 1: add new IP, if this fails we cancel
1442 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1443 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1450 # step 2: replace gateway
1451 # If this fails we delete the added IP and cancel.
1452 # If it succeeds we save the config and delete the old IP, ignoring
1453 # errors. The config is then saved.
1454 # Note: 'ip route replace' can add
1457 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1460 # the route was not replaced, the old IP is still available
1461 # rollback (delete new IP) and cancel
1463 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1464 warn $@ if $@; # no need to die here
1469 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1470 # if the route was not deleted, the guest might have deleted it manually
1476 # from this point on we save the configuration
1477 # step 3: delete old IP ignoring errors
1478 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1479 # We need to enable promote_secondaries, otherwise our newly added
1480 # address will be removed along with the old one.
1483 if ($ipversion == 4) {
1484 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1485 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1486 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1488 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1490 warn $@ if $@; # no need to die here
1492 if ($ipversion == 4) {
1493 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1497 foreach my $property ($ip, $gw) {
1498 if ($newnet->{$property}) {
1499 $optdata->{$property} = $newnet->{$property};
1501 delete $optdata->{$property};
1504 $conf->{$opt} = print_lxc_network
($optdata);
1505 PVE
::LXC
::write_config
($vmid, $conf);
1506 $lxc_setup->setup_network($conf);
1509 &$change_ip_config(4);
1510 &$change_ip_config(6);
1514 # Internal snapshots
1516 # NOTE: Snapshot create/delete involves several non-atomic
1517 # action, and can take a long time.
1518 # So we try to avoid locking the file and use 'lock' variable
1519 # inside the config file instead.
1521 my $snapshot_copy_config = sub {
1522 my ($source, $dest) = @_;
1524 foreach my $k (keys %$source) {
1525 next if $k eq 'snapshots';
1526 next if $k eq 'snapstate';
1527 next if $k eq 'snaptime';
1528 next if $k eq 'vmstate';
1529 next if $k eq 'lock';
1530 next if $k eq 'digest';
1531 next if $k eq 'description';
1533 $dest->{$k} = $source->{$k};
1537 my $snapshot_prepare = sub {
1538 my ($vmid, $snapname, $comment) = @_;
1542 my $updatefn = sub {
1544 my $conf = load_config
($vmid);
1546 die "you can't take a snapshot if it's a template\n"
1547 if is_template
($conf);
1551 $conf->{lock} = 'snapshot';
1553 die "snapshot name '$snapname' already used\n"
1554 if defined($conf->{snapshots
}->{$snapname});
1556 my $storecfg = PVE
::Storage
::config
();
1557 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1559 $snap = $conf->{snapshots
}->{$snapname} = {};
1561 &$snapshot_copy_config($conf, $snap);
1563 $snap->{'snapstate'} = "prepare";
1564 $snap->{'snaptime'} = time();
1565 $snap->{'description'} = $comment if $comment;
1566 $conf->{snapshots
}->{$snapname} = $snap;
1568 PVE
::LXC
::write_config
($vmid, $conf);
1571 lock_container
($vmid, 10, $updatefn);
1576 my $snapshot_commit = sub {
1577 my ($vmid, $snapname) = @_;
1579 my $updatefn = sub {
1581 my $conf = load_config
($vmid);
1583 die "missing snapshot lock\n"
1584 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1586 die "snapshot '$snapname' does not exist\n"
1587 if !defined($conf->{snapshots
}->{$snapname});
1589 die "wrong snapshot state\n"
1590 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1591 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1593 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1594 delete $conf->{lock};
1595 $conf->{parent
} = $snapname;
1597 PVE
::LXC
::write_config
($vmid, $conf);
1600 lock_container
($vmid, 10 ,$updatefn);
1604 my ($feature, $conf, $storecfg, $snapname) = @_;
1606 #Fixme add other drives if necessary.
1609 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1610 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $rootinfo->{volume
}, $snapname);
1612 return $err ?
0 : 1;
1615 sub snapshot_create
{
1616 my ($vmid, $snapname, $comment) = @_;
1618 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1620 my $conf = load_config
($vmid);
1622 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1623 my $running = check_running
($vmid);
1626 PVE
::Tools
::run_command
($cmd);
1629 my $storecfg = PVE
::Storage
::config
();
1630 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1631 my $volid = $rootinfo->{volume
};
1633 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1635 PVE
::Tools
::run_command
($cmd);
1638 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1639 &$snapshot_commit($vmid, $snapname);
1642 snapshot_delete
($vmid, $snapname, 1);
1647 sub snapshot_delete
{
1648 my ($vmid, $snapname, $force) = @_;
1654 my $updatefn = sub {
1656 $conf = load_config
($vmid);
1658 die "you can't delete a snapshot if vm is a template\n"
1659 if is_template
($conf);
1661 $snap = $conf->{snapshots
}->{$snapname};
1665 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1667 $snap->{snapstate
} = 'delete';
1669 PVE
::LXC
::write_config
($vmid, $conf);
1672 lock_container
($vmid, 10, $updatefn);
1674 my $storecfg = PVE
::Storage
::config
();
1676 my $del_snap = sub {
1680 if ($conf->{parent
} eq $snapname) {
1681 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1682 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1684 delete $conf->{parent
};
1688 delete $conf->{snapshots
}->{$snapname};
1690 PVE
::LXC
::write_config
($vmid, $conf);
1693 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1694 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1695 my $volid = $rootinfo->{volume
};
1698 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1702 if(!$err || ($err && $force)) {
1703 lock_container
($vmid, 10, $del_snap);
1705 die "Can't delete snapshot: $vmid $snapname $err\n";
1710 sub snapshot_rollback
{
1711 my ($vmid, $snapname) = @_;
1713 my $storecfg = PVE
::Storage
::config
();
1715 my $conf = load_config
($vmid);
1717 die "you can't rollback if vm is a template\n" if is_template
($conf);
1719 my $snap = $conf->{snapshots
}->{$snapname};
1721 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1723 my $rootfs = $snap->{rootfs
};
1724 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1725 my $volid = $rootinfo->{volume
};
1727 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1729 my $updatefn = sub {
1731 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1732 if $snap->{snapstate
};
1736 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1738 die "unable to rollback vm $vmid: vm is running\n"
1739 if check_running
($vmid);
1741 $conf->{lock} = 'rollback';
1745 # copy snapshot config to current config
1747 my $tmp_conf = $conf;
1748 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1749 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1750 delete $conf->{snaptime
};
1751 delete $conf->{snapname
};
1752 $conf->{parent
} = $snapname;
1754 PVE
::LXC
::write_config
($vmid, $conf);
1757 my $unlockfn = sub {
1758 delete $conf->{lock};
1759 PVE
::LXC
::write_config
($vmid, $conf);
1762 lock_container
($vmid, 10, $updatefn);
1764 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1766 lock_container
($vmid, 5, $unlockfn);
1769 sub template_create
{
1770 my ($vmid, $conf) = @_;
1772 my $storecfg = PVE
::Storage
::config
();
1774 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1775 my $volid = $rootinfo->{volume
};
1777 die "Template feature is not available for '$volid'\n"
1778 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1780 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1782 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1783 $rootinfo->{volume
} = $template_volid;
1784 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo);
1786 write_config
($vmid, $conf);
1792 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1795 sub foreach_mountpoint
{
1796 my ($conf, $func) = @_;
1798 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1799 $mountpoint->{mp
} = '/'; # just to be sure
1800 &$func('rootfs', $mountpoint);
1802 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1804 next if !defined($conf->{$key});
1805 $mountpoint = parse_ct_mountpoint
($conf->{$key});
1806 &$func($key, $mountpoint);
1810 sub loopdevices_list
{
1815 if ($line =~ m/^(\/dev\
/loop\d+)\s+\d\s+\d\s+\d\s+\d\s(\S+)$/) {
1816 $loopdev->{$1} = $2;
1820 PVE
::Tools
::run_command
(['losetup'], outfunc
=> $parser);
1825 sub blockdevices_list
{
1828 dir_glob_foreach
("/sys/dev/block/", '(\d+):(\d+)', sub {
1829 my (undef, $major, $minor) = @_;
1830 my $bdev = readlink("/sys/dev/block/$major:$minor");
1831 $bdev =~ s/\.\.\/\.\.\/devices\
/virtual\/block\
//\
/dev\//;
1832 $bdevs->{$bdev}->{major
} = $major;
1833 $bdevs->{$bdev}->{minor
} = $minor;
1839 my ($loopdevs, $path) = @_;
1841 foreach my $dev (keys %$loopdevs){
1842 return $dev if $loopdevs->{$dev} eq $path;
1846 sub check_ct_modify_config_perm
{
1847 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1849 return 1 if $authuser ne 'root@pam';
1851 foreach my $opt (@$key_list) {
1853 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1854 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1855 } elsif ($opt eq 'disk') {
1856 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1857 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1858 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1859 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1860 $opt eq 'searchdomain' || $opt eq 'hostname') {
1861 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1863 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1872 my ($volid, $ms, $storage_cfg, $loopdevs) = @_;
1874 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1875 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1876 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
1878 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1879 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1881 die "unable to use template as mountpoint\n" if $isBase;
1883 if ($format eq 'subvol') {
1885 } elsif ($format eq 'raw') {
1887 if ($scfg->{path
}) {
1888 if ($ms eq 'rootfs') {
1889 $path = "loop:$path\n" if $ms eq 'rootfs';
1890 } elsif ($loopdevs) {
1891 $path = PVE
::LXC
::find_loopdev
($loopdevs, $path) if $loopdevs;
1894 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
1897 die "unsupported storage type '$scfg->{type}'\n";
1900 die "unsupported image format '$format'\n";