12 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
16 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach);
19 use PVE
::AccessControl
;
24 my $nodename = PVE
::INotify
::nodename
();
26 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
32 format_description
=> 'volume',
33 description
=> 'Volume, device or directory to mount into the container.',
37 format_description
=> '[1|0]',
38 description
=> 'Whether to include the mountpoint in backups.',
43 format
=> 'disk-size',
44 format_description
=> 'DiskSize',
45 description
=> 'Volume size (read only value).',
50 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
51 type
=> 'string', format
=> $rootfs_desc,
52 description
=> "Use volume as container root.",
56 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
57 description
=> "The name of the snapshot.",
58 type
=> 'string', format
=> 'pve-configid',
66 description
=> "Lock/unlock the VM.",
67 enum
=> [qw(migrate backup snapshot rollback)],
72 description
=> "Specifies whether a VM will be started during system bootup.",
75 startup
=> get_standard_option
('pve-startup-order'),
79 description
=> "Enable/disable Template.",
85 enum
=> ['amd64', 'i386'],
86 description
=> "OS architecture type.",
92 enum
=> ['debian', 'ubuntu', 'centos', 'archlinux'],
93 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
98 description
=> "Attach a console device (/dev/console) to the container.",
104 description
=> "Specify the number of tty available to the container",
112 description
=> "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
120 description
=> "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
128 description
=> "Amount of RAM for the VM in MB.",
135 description
=> "Amount of SWAP for the VM in MB.",
141 description
=> "Set a host name for the container.",
142 type
=> 'string', format
=> 'dns-name',
148 description
=> "Container description. Only used on the configuration web interface.",
152 type
=> 'string', format
=> 'dns-name-list',
153 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
157 type
=> 'string', format
=> 'address-list',
158 description
=> "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
160 rootfs
=> get_standard_option
('pve-ct-rootfs'),
163 type
=> 'string', format
=> 'pve-configid',
165 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
169 description
=> "Timestamp for snapshots.",
175 description
=> "Console mode. By default, the console command tries to open a connection to one of the available tty devices. By setting cmode to 'console' it tries to attach to /dev/console instead. If you set cmode to 'shell', it simply invokes a shell inside the container (no login).",
177 enum
=> ['shell', 'console', 'tty'],
183 description
=> "Sets the protection flag of the container. This will prevent the remove operation. This will prevent the CT or CT's disk remove/update operation.",
188 my $valid_lxc_conf_keys = {
192 'lxc.haltsignal' => 1,
193 'lxc.rebootsignal' => 1,
194 'lxc.stopsignal' => 1,
196 'lxc.network.type' => 1,
197 'lxc.network.flags' => 1,
198 'lxc.network.link' => 1,
199 'lxc.network.mtu' => 1,
200 'lxc.network.name' => 1,
201 'lxc.network.hwaddr' => 1,
202 'lxc.network.ipv4' => 1,
203 'lxc.network.ipv4.gateway' => 1,
204 'lxc.network.ipv6' => 1,
205 'lxc.network.ipv6.gateway' => 1,
206 'lxc.network.script.up' => 1,
207 'lxc.network.script.down' => 1,
209 'lxc.console.logfile' => 1,
212 'lxc.devttydir' => 1,
213 'lxc.hook.autodev' => 1,
217 'lxc.mount.entry' => 1,
218 'lxc.mount.auto' => 1,
220 'lxc.rootfs.mount' => 1,
221 'lxc.rootfs.options' => 1,
225 'lxc.aa_profile' => 1,
226 'lxc.aa_allow_incomplete' => 1,
227 'lxc.se_context' => 1,
230 'lxc.hook.pre-start' => 1,
231 'lxc.hook.pre-mount' => 1,
232 'lxc.hook.mount' => 1,
233 'lxc.hook.start' => 1,
234 'lxc.hook.stop' => 1,
235 'lxc.hook.post-stop' => 1,
236 'lxc.hook.clone' => 1,
237 'lxc.hook.destroy' => 1,
240 'lxc.start.auto' => 1,
241 'lxc.start.delay' => 1,
242 'lxc.start.order' => 1,
244 'lxc.environment' => 1,
255 description
=> "Network interface type.",
260 format_description
=> 'String',
261 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
262 pattern
=> '[-_.\w\d]+',
266 format_description
=> 'vmbr<Number>',
267 description
=> 'Bridge to attach the network device to.',
268 pattern
=> '[-_.\w\d]+',
273 format_description
=> 'MAC',
274 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
275 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
280 format_description
=> 'Number',
281 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
282 minimum
=> 64, # minimum ethernet frame is 64 bytes
287 format
=> 'pve-ipv4-config',
288 format_description
=> 'IPv4Format/CIDR',
289 description
=> 'IPv4 address in CIDR format.',
295 format_description
=> 'GatewayIPv4',
296 description
=> 'Default gateway for IPv4 traffic.',
301 format
=> 'pve-ipv6-config',
302 format_description
=> 'IPv6Format/CIDR',
303 description
=> 'IPv6 address in CIDR format.',
309 format_description
=> 'GatewayIPv6',
310 description
=> 'Default gateway for IPv6 traffic.',
315 format_description
=> '[1|0]',
316 description
=> "Controls whether this interface's firewall rules should be used.",
321 format_description
=> 'VlanNo',
324 description
=> "VLAN tag foro this interface.",
328 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
330 my $MAX_LXC_NETWORKS = 10;
331 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
332 $confdesc->{"net$i"} = {
334 type
=> 'string', format
=> $netconf_desc,
335 description
=> "Specifies network interfaces for the container.",
343 format_description
=> 'Path',
344 description
=> 'Path to the mountpoint as seen from inside the container.',
348 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
352 type
=> 'string', format
=> 'pve-volume-id',
353 description
=> "Reference to unused volumes.",
356 my $MAX_MOUNT_POINTS = 10;
357 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
358 $confdesc->{"mp$i"} = {
360 type
=> 'string', format
=> $mp_desc,
361 description
=> "Use volume as container mount point (experimental feature).",
366 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
367 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
368 $confdesc->{"unused$i"} = $unuseddesc;
371 sub write_pct_config
{
372 my ($filename, $conf) = @_;
374 delete $conf->{snapstate
}; # just to be sure
376 my $generate_raw_config = sub {
381 # add description as comment to top of file
382 my $descr = $conf->{description
} || '';
383 foreach my $cl (split(/\n/, $descr)) {
384 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
387 foreach my $key (sort keys %$conf) {
388 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
389 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
390 my $value = $conf->{$key};
391 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
392 $raw .= "$key: $value\n";
395 if (my $lxcconf = $conf->{lxc
}) {
396 foreach my $entry (@$lxcconf) {
397 my ($k, $v) = @$entry;
405 my $raw = &$generate_raw_config($conf);
407 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
408 $raw .= "\n[$snapname]\n";
409 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
416 my ($key, $value) = @_;
418 die "unknown setting '$key'\n" if !$confdesc->{$key};
420 my $type = $confdesc->{$key}->{type
};
422 if (!defined($value)) {
423 die "got undefined value\n";
426 if ($value =~ m/[\n\r]/) {
427 die "property contains a line feed\n";
430 if ($type eq 'boolean') {
431 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
432 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
433 die "type check ('boolean') failed - got '$value'\n";
434 } elsif ($type eq 'integer') {
435 return int($1) if $value =~ m/^(\d+)$/;
436 die "type check ('integer') failed - got '$value'\n";
437 } elsif ($type eq 'number') {
438 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
439 die "type check ('number') failed - got '$value'\n";
440 } elsif ($type eq 'string') {
441 if (my $fmt = $confdesc->{$key}->{format
}) {
442 PVE
::JSONSchema
::check_format
($fmt, $value);
451 sub parse_pct_config
{
452 my ($filename, $raw) = @_;
454 return undef if !defined($raw);
457 digest
=> Digest
::SHA
::sha1_hex
($raw),
461 $filename =~ m
|/lxc/(\d
+).conf
$|
462 || die "got strange filename '$filename'";
470 my @lines = split(/\n/, $raw);
471 foreach my $line (@lines) {
472 next if $line =~ m/^\s*$/;
474 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
476 $conf->{description
} = $descr if $descr;
478 $conf = $res->{snapshots
}->{$section} = {};
482 if ($line =~ m/^\#(.*)\s*$/) {
483 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
487 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
490 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
491 push @{$conf->{lxc
}}, [$key, $value];
493 warn "vm $vmid - unable to parse config: $line\n";
495 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
496 $descr .= PVE
::Tools
::decode_text
($2);
497 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
498 $conf->{snapstate
} = $1;
499 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
502 eval { $value = check_type
($key, $value); };
503 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
504 $conf->{$key} = $value;
506 warn "vm $vmid - unable to parse config: $line\n";
510 $conf->{description
} = $descr if $descr;
512 delete $res->{snapstate
}; # just to be sure
518 my $vmlist = PVE
::Cluster
::get_vmlist
();
520 return $res if !$vmlist || !$vmlist->{ids
};
521 my $ids = $vmlist->{ids
};
523 foreach my $vmid (keys %$ids) {
524 next if !$vmid; # skip CT0
525 my $d = $ids->{$vmid};
526 next if !$d->{node
} || $d->{node
} ne $nodename;
527 next if !$d->{type
} || $d->{type
} ne 'lxc';
528 $res->{$vmid}->{type
} = 'lxc';
533 sub cfs_config_path
{
534 my ($vmid, $node) = @_;
536 $node = $nodename if !$node;
537 return "nodes/$node/lxc/$vmid.conf";
541 my ($vmid, $node) = @_;
543 my $cfspath = cfs_config_path
($vmid, $node);
544 return "/etc/pve/$cfspath";
548 my ($vmid, $node) = @_;
550 $node = $nodename if !$node;
551 my $cfspath = cfs_config_path
($vmid, $node);
553 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
554 die "container $vmid does not exists\n" if !defined($conf);
560 my ($vmid, $conf) = @_;
562 my $dir = "/etc/pve/nodes/$nodename/lxc";
565 write_config
($vmid, $conf);
571 unlink config_file
($vmid, $nodename);
575 my ($vmid, $conf) = @_;
577 my $cfspath = cfs_config_path
($vmid);
579 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
582 # flock: we use one file handle per process, so lock file
583 # can be called multiple times and succeeds for the same process.
585 my $lock_handles = {};
586 my $lockdir = "/run/lock/lxc";
591 return "$lockdir/pve-config-${vmid}.lock";
595 my ($vmid, $timeout) = @_;
597 $timeout = 10 if !$timeout;
600 my $filename = lock_filename
($vmid);
602 mkdir $lockdir if !-d
$lockdir;
604 my $lock_func = sub {
605 if (!$lock_handles->{$$}->{$filename}) {
606 my $fh = new IO
::File
(">>$filename") ||
607 die "can't open file - $!\n";
608 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
611 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
612 print STDERR
"trying to aquire lock...";
615 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
616 # try again on EINTR (see bug #273)
617 if ($success || ($! != EINTR
)) {
622 print STDERR
" failed\n";
623 die "can't aquire lock - $!\n";
626 print STDERR
" OK\n";
629 $lock_handles->{$$}->{$filename}->{refcount
}++;
632 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
635 die "can't lock file '$filename' - $err";
642 my $filename = lock_filename
($vmid);
644 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
645 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
646 if ($refcount <= 0) {
647 $lock_handles->{$$}->{$filename} = undef;
654 my ($vmid, $timeout, $code, @param) = @_;
658 lock_aquire
($vmid, $timeout);
659 eval { $res = &$code(@param) };
671 return defined($confdesc->{$name});
674 # add JSON properties for create and set function
675 sub json_config_properties
{
678 foreach my $opt (keys %$confdesc) {
679 next if $opt eq 'parent' || $opt eq 'snaptime';
680 next if $prop->{$opt};
681 $prop->{$opt} = $confdesc->{$opt};
687 sub json_config_properties_no_rootfs
{
690 foreach my $opt (keys %$confdesc) {
691 next if $prop->{$opt};
692 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
693 $prop->{$opt} = $confdesc->{$opt};
699 # container status helpers
701 sub list_active_containers
{
703 my $filename = "/proc/net/unix";
705 # similar test is used by lcxcontainers.c: list_active_containers
708 my $fh = IO
::File-
>new ($filename, "r");
711 while (defined(my $line = <$fh>)) {
712 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
714 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
725 # warning: this is slow
729 my $active_hash = list_active_containers
();
731 return 1 if defined($active_hash->{$vmid});
736 sub get_container_disk_usage
{
739 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
749 if (my ($fsid, $total, $used, $avail) = $line =~
750 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
758 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
767 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
769 my $active_hash = list_active_containers
();
771 foreach my $vmid (keys %$list) {
772 my $d = $list->{$vmid};
774 my $running = defined($active_hash->{$vmid});
776 $d->{status
} = $running ?
'running' : 'stopped';
778 my $cfspath = cfs_config_path
($vmid);
779 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
781 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
782 $d->{name
} =~ s/[\s]//g;
784 $d->{cpus
} = $conf->{cpulimit
} // 0;
787 my $res = get_container_disk_usage
($vmid);
788 $d->{disk
} = $res->{used
};
789 $d->{maxdisk
} = $res->{total
};
792 # use 4GB by default ??
793 if (my $rootfs = $conf->{rootfs
}) {
794 my $rootinfo = parse_ct_mountpoint
($rootfs);
795 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
797 $d->{maxdisk
} = 4*1024*1024*1024;
803 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
804 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
815 $d->{template
} = is_template
($conf);
818 foreach my $vmid (keys %$list) {
819 my $d = $list->{$vmid};
820 next if $d->{status
} ne 'running';
822 my $pid = find_lxc_pid
($vmid);
823 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
824 $d->{uptime
} = time - $ctime; # the method lxcfs uses
826 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
827 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
829 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
830 my @bytes = split(/\n/, $blkio_bytes);
831 foreach my $byte (@bytes) {
832 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
833 $d->{diskread
} = $2 if $key eq 'Read';
834 $d->{diskwrite
} = $2 if $key eq 'Write';
842 sub parse_ct_mountpoint
{
843 my ($data, $noerr) = @_;
848 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
850 return undef if $noerr;
854 if (defined(my $size = $res->{size
})) {
855 $size = PVE
::JSONSchema
::parse_size
($size);
856 if (!defined($size)) {
857 return undef if $noerr;
858 die "invalid size: $size\n";
860 $res->{size
} = $size;
866 sub print_ct_mountpoint
{
867 my ($info, $nomp) = @_;
868 my $skip = $nomp ?
['mp'] : [];
869 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
872 sub print_lxc_network
{
874 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
877 sub parse_lxc_network
{
882 return $res if !$data;
884 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
886 $res->{type
} = 'veth';
887 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
892 sub read_cgroup_value
{
893 my ($group, $vmid, $name, $full) = @_;
895 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
897 return PVE
::Tools
::file_get_contents
($path) if $full;
899 return PVE
::Tools
::file_read_firstline
($path);
902 sub write_cgroup_value
{
903 my ($group, $vmid, $name, $value) = @_;
905 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
906 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
910 sub find_lxc_console_pids
{
914 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
917 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
920 my @args = split(/\0/, $cmdline);
922 # serach for lxc-console -n <vmid>
923 return if scalar(@args) != 3;
924 return if $args[1] ne '-n';
925 return if $args[2] !~ m/^\d+$/;
926 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
930 push @{$res->{$vmid}}, $pid;
942 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
944 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
946 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
951 # Note: we cannot use Net:IP, because that only allows strict
953 sub parse_ipv4_cidr
{
954 my ($cidr, $noerr) = @_;
956 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
957 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
960 return undef if $noerr;
962 die "unable to parse ipv4 address/mask\n";
968 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
971 sub check_protection
{
972 my ($vm_conf, $err_msg) = @_;
974 if ($vm_conf->{protection
}) {
975 die "$err_msg - protection mode enabled\n";
979 sub update_lxc_config
{
980 my ($storage_cfg, $vmid, $conf) = @_;
982 my $dir = "/var/lib/lxc/$vmid";
984 if ($conf->{template
}) {
986 unlink "$dir/config";
993 die "missing 'arch' - internal error" if !$conf->{arch
};
994 $raw .= "lxc.arch = $conf->{arch}\n";
996 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
997 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
998 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1003 if (!has_dev_console
($conf)) {
1004 $raw .= "lxc.console = none\n";
1005 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1008 my $ttycount = get_tty_count
($conf);
1009 $raw .= "lxc.tty = $ttycount\n";
1011 # some init scripts expects a linux terminal (turnkey).
1012 $raw .= "lxc.environment = TERM=linux\n";
1014 my $utsname = $conf->{hostname
} || "CT$vmid";
1015 $raw .= "lxc.utsname = $utsname\n";
1017 my $memory = $conf->{memory
} || 512;
1018 my $swap = $conf->{swap
} // 0;
1020 my $lxcmem = int($memory*1024*1024);
1021 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1023 my $lxcswap = int(($memory + $swap)*1024*1024);
1024 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1026 if (my $cpulimit = $conf->{cpulimit
}) {
1027 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1028 my $value = int(100000*$cpulimit);
1029 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1032 my $shares = $conf->{cpuunits
} || 1024;
1033 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1035 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1036 $mountpoint->{mp
} = '/';
1038 my ($path, $use_loopdev) = mountpoint_mount_path
($mountpoint, $storage_cfg);
1039 $path = "loop:$path" if $use_loopdev;
1041 $raw .= "lxc.rootfs = $path\n";
1044 foreach my $k (keys %$conf) {
1045 next if $k !~ m/^net(\d+)$/;
1047 my $d = parse_lxc_network
($conf->{$k});
1049 $raw .= "lxc.network.type = veth\n";
1050 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1051 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1052 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1053 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1056 if (my $lxcconf = $conf->{lxc
}) {
1057 foreach my $entry (@$lxcconf) {
1058 my ($k, $v) = @$entry;
1059 $netcount++ if $k eq 'lxc.network.type';
1060 $raw .= "$k = $v\n";
1064 $raw .= "lxc.network.type = empty\n" if !$netcount;
1066 File
::Path
::mkpath
("$dir/rootfs");
1068 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1071 # verify and cleanup nameserver list (replace \0 with ' ')
1072 sub verify_nameserver_list
{
1073 my ($nameserver_list) = @_;
1076 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1077 PVE
::JSONSchema
::pve_verify_ip
($server);
1078 push @list, $server;
1081 return join(' ', @list);
1084 sub verify_searchdomain_list
{
1085 my ($searchdomain_list) = @_;
1088 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1089 # todo: should we add checks for valid dns domains?
1090 push @list, $server;
1093 return join(' ', @list);
1096 sub add_unused_volume
{
1097 my ($config, $volid) = @_;
1099 # skip bind mounts and block devices
1100 return if $volid =~ m
|^/|;
1103 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1104 my $test = "unused$ind";
1105 if (my $vid = $config->{$test}) {
1106 return if $vid eq $volid; # do not add duplicates
1112 die "To many unused volume - please delete them first.\n" if !$key;
1114 $config->{$key} = $volid;
1119 sub update_pct_config
{
1120 my ($vmid, $conf, $running, $param, $delete) = @_;
1125 my @deleted_volumes;
1129 my $pid = find_lxc_pid
($vmid);
1130 $rootdir = "/proc/$pid/root";
1133 my $hotplug_error = sub {
1135 push @nohotplug, @_;
1142 if (defined($delete)) {
1143 foreach my $opt (@$delete) {
1144 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1145 die "unable to delete required option '$opt'\n";
1146 } elsif ($opt eq 'swap') {
1147 delete $conf->{$opt};
1148 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1149 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1150 delete $conf->{$opt};
1151 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1152 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1153 next if $hotplug_error->($opt);
1154 delete $conf->{$opt};
1155 } elsif ($opt =~ m/^net(\d)$/) {
1156 delete $conf->{$opt};
1159 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1160 } elsif ($opt eq 'protection') {
1161 delete $conf->{$opt};
1162 } elsif ($opt =~ m/^unused(\d+)$/) {
1163 next if $hotplug_error->($opt);
1164 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1165 push @deleted_volumes, $conf->{$opt};
1166 delete $conf->{$opt};
1167 } elsif ($opt =~ m/^mp(\d+)$/) {
1168 next if $hotplug_error->($opt);
1169 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1170 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1171 add_unused_volume
($conf, $mountpoint->{volume
});
1172 delete $conf->{$opt};
1176 write_config
($vmid, $conf) if $running;
1180 # There's no separate swap size to configure, there's memory and "total"
1181 # memory (iow. memory+swap). This means we have to change them together.
1182 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1183 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1184 if (defined($wanted_memory) || defined($wanted_swap)) {
1186 $wanted_memory //= ($conf->{memory
} || 512);
1187 $wanted_swap //= ($conf->{swap
} || 0);
1189 my $total = $wanted_memory + $wanted_swap;
1191 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1192 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1194 $conf->{memory
} = $wanted_memory;
1195 $conf->{swap
} = $wanted_swap;
1197 write_config
($vmid, $conf) if $running;
1200 foreach my $opt (keys %$param) {
1201 my $value = $param->{$opt};
1202 if ($opt eq 'hostname') {
1203 $conf->{$opt} = $value;
1204 } elsif ($opt eq 'onboot') {
1205 $conf->{$opt} = $value ?
1 : 0;
1206 } elsif ($opt eq 'startup') {
1207 $conf->{$opt} = $value;
1208 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1209 next if $hotplug_error->($opt);
1210 $conf->{$opt} = $value;
1211 } elsif ($opt eq 'nameserver') {
1212 next if $hotplug_error->($opt);
1213 my $list = verify_nameserver_list
($value);
1214 $conf->{$opt} = $list;
1215 } elsif ($opt eq 'searchdomain') {
1216 next if $hotplug_error->($opt);
1217 my $list = verify_searchdomain_list
($value);
1218 $conf->{$opt} = $list;
1219 } elsif ($opt eq 'cpulimit') {
1220 next if $hotplug_error->($opt); # FIXME: hotplug
1221 $conf->{$opt} = $value;
1222 } elsif ($opt eq 'cpuunits') {
1223 $conf->{$opt} = $value;
1224 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1225 } elsif ($opt eq 'description') {
1226 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1227 } elsif ($opt =~ m/^net(\d+)$/) {
1229 my $net = parse_lxc_network
($value);
1231 $conf->{$opt} = print_lxc_network
($net);
1233 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1235 } elsif ($opt eq 'protection') {
1236 $conf->{$opt} = $value ?
1 : 0;
1237 } elsif ($opt =~ m/^mp(\d+)$/) {
1238 next if $hotplug_error->($opt);
1239 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1240 $conf->{$opt} = $value;
1242 } elsif ($opt eq 'rootfs') {
1243 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1244 die "implement me: $opt";
1246 die "implement me: $opt";
1248 write_config
($vmid, $conf) if $running;
1251 if (@deleted_volumes) {
1252 my $storage_cfg = PVE
::Storage
::config
();
1253 foreach my $volume (@deleted_volumes) {
1254 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1259 my $storage_cfg = PVE
::Storage
::config
();
1260 create_disks
($storage_cfg, $vmid, $conf, $conf);
1263 # This should be the last thing we do here
1264 if ($running && scalar(@nohotplug)) {
1265 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1269 sub has_dev_console
{
1272 return !(defined($conf->{console
}) && !$conf->{console
});
1278 return $conf->{tty
} // $confdesc->{tty
}->{default};
1284 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1287 sub get_console_command
{
1288 my ($vmid, $conf) = @_;
1290 my $cmode = get_cmode
($conf);
1292 if ($cmode eq 'console') {
1293 return ['lxc-console', '-n', $vmid, '-t', 0];
1294 } elsif ($cmode eq 'tty') {
1295 return ['lxc-console', '-n', $vmid];
1296 } elsif ($cmode eq 'shell') {
1297 return ['lxc-attach', '--clear-env', '-n', $vmid];
1299 die "internal error";
1303 sub get_primary_ips
{
1306 # return data from net0
1308 return undef if !defined($conf->{net0
});
1309 my $net = parse_lxc_network
($conf->{net0
});
1311 my $ipv4 = $net->{ip
};
1313 if ($ipv4 =~ /^(dhcp|manual)$/) {
1319 my $ipv6 = $net->{ip6
};
1321 if ($ipv6 =~ /^(dhcp|manual)$/) {
1328 return ($ipv4, $ipv6);
1331 sub delete_mountpoint_volume
{
1332 my ($storage_cfg, $vmid, $volume) = @_;
1334 # skip bind mounts and block devices
1335 if ($volume =~ m
|^/|) {
1339 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1340 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1343 sub destroy_lxc_container
{
1344 my ($storage_cfg, $vmid, $conf) = @_;
1346 foreach_mountpoint
($conf, sub {
1347 my ($ms, $mountpoint) = @_;
1348 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1351 rmdir "/var/lib/lxc/$vmid/rootfs";
1352 unlink "/var/lib/lxc/$vmid/config";
1353 rmdir "/var/lib/lxc/$vmid";
1354 destroy_config
($vmid);
1356 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1357 #PVE::Tools::run_command($cmd);
1360 sub vm_stop_cleanup
{
1361 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1366 my $vollist = get_vm_volumes
($conf);
1367 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1370 warn $@ if $@; # avoid errors - just warn
1373 my $safe_num_ne = sub {
1376 return 0 if !defined($a) && !defined($b);
1377 return 1 if !defined($a);
1378 return 1 if !defined($b);
1383 my $safe_string_ne = sub {
1386 return 0 if !defined($a) && !defined($b);
1387 return 1 if !defined($a);
1388 return 1 if !defined($b);
1394 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1396 if ($newnet->{type
} ne 'veth') {
1397 # for when there are physical interfaces
1398 die "cannot update interface of type $newnet->{type}";
1401 my $veth = "veth${vmid}i${netid}";
1402 my $eth = $newnet->{name
};
1404 if (my $oldnetcfg = $conf->{$opt}) {
1405 my $oldnet = parse_lxc_network
($oldnetcfg);
1407 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1408 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1410 PVE
::Network
::veth_delete
($veth);
1411 delete $conf->{$opt};
1412 write_config
($vmid, $conf);
1414 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1416 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1417 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1418 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1420 if ($oldnet->{bridge
}) {
1421 PVE
::Network
::tap_unplug
($veth);
1422 foreach (qw(bridge tag firewall)) {
1423 delete $oldnet->{$_};
1425 $conf->{$opt} = print_lxc_network
($oldnet);
1426 write_config
($vmid, $conf);
1429 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1430 foreach (qw(bridge tag firewall)) {
1431 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1433 $conf->{$opt} = print_lxc_network
($oldnet);
1434 write_config
($vmid, $conf);
1437 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1440 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1444 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1446 my $veth = "veth${vmid}i${netid}";
1447 my $vethpeer = $veth . "p";
1448 my $eth = $newnet->{name
};
1450 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1451 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1453 # attach peer in container
1454 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1455 PVE
::Tools
::run_command
($cmd);
1457 # link up peer in container
1458 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1459 PVE
::Tools
::run_command
($cmd);
1461 my $done = { type
=> 'veth' };
1462 foreach (qw(bridge tag firewall hwaddr name)) {
1463 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1465 $conf->{$opt} = print_lxc_network
($done);
1467 write_config
($vmid, $conf);
1470 sub update_ipconfig
{
1471 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1473 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1475 my $optdata = parse_lxc_network
($conf->{$opt});
1479 my $cmdargs = shift;
1480 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1482 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1484 my $change_ip_config = sub {
1485 my ($ipversion) = @_;
1487 my $family_opt = "-$ipversion";
1488 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1489 my $gw= "gw$suffix";
1490 my $ip= "ip$suffix";
1492 my $newip = $newnet->{$ip};
1493 my $newgw = $newnet->{$gw};
1494 my $oldip = $optdata->{$ip};
1496 my $change_ip = &$safe_string_ne($oldip, $newip);
1497 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1499 return if !$change_ip && !$change_gw;
1501 # step 1: add new IP, if this fails we cancel
1502 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1503 if ($change_ip && $is_real_ip) {
1504 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1511 # step 2: replace gateway
1512 # If this fails we delete the added IP and cancel.
1513 # If it succeeds we save the config and delete the old IP, ignoring
1514 # errors. The config is then saved.
1515 # Note: 'ip route replace' can add
1519 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1520 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1522 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1526 # the route was not replaced, the old IP is still available
1527 # rollback (delete new IP) and cancel
1529 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1530 warn $@ if $@; # no need to die here
1535 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1536 # if the route was not deleted, the guest might have deleted it manually
1542 # from this point on we save the configuration
1543 # step 3: delete old IP ignoring errors
1544 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1545 # We need to enable promote_secondaries, otherwise our newly added
1546 # address will be removed along with the old one.
1549 if ($ipversion == 4) {
1550 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1551 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1552 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1554 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1556 warn $@ if $@; # no need to die here
1558 if ($ipversion == 4) {
1559 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1563 foreach my $property ($ip, $gw) {
1564 if ($newnet->{$property}) {
1565 $optdata->{$property} = $newnet->{$property};
1567 delete $optdata->{$property};
1570 $conf->{$opt} = print_lxc_network
($optdata);
1571 write_config
($vmid, $conf);
1572 $lxc_setup->setup_network($conf);
1575 &$change_ip_config(4);
1576 &$change_ip_config(6);
1580 # Internal snapshots
1582 # NOTE: Snapshot create/delete involves several non-atomic
1583 # action, and can take a long time.
1584 # So we try to avoid locking the file and use 'lock' variable
1585 # inside the config file instead.
1587 my $snapshot_copy_config = sub {
1588 my ($source, $dest) = @_;
1590 foreach my $k (keys %$source) {
1591 next if $k eq 'snapshots';
1592 next if $k eq 'snapstate';
1593 next if $k eq 'snaptime';
1594 next if $k eq 'vmstate';
1595 next if $k eq 'lock';
1596 next if $k eq 'digest';
1597 next if $k eq 'description';
1599 $dest->{$k} = $source->{$k};
1603 my $snapshot_prepare = sub {
1604 my ($vmid, $snapname, $comment) = @_;
1608 my $updatefn = sub {
1610 my $conf = load_config
($vmid);
1612 die "you can't take a snapshot if it's a template\n"
1613 if is_template
($conf);
1617 $conf->{lock} = 'snapshot';
1619 die "snapshot name '$snapname' already used\n"
1620 if defined($conf->{snapshots
}->{$snapname});
1622 my $storecfg = PVE
::Storage
::config
();
1623 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1625 $snap = $conf->{snapshots
}->{$snapname} = {};
1627 &$snapshot_copy_config($conf, $snap);
1629 $snap->{'snapstate'} = "prepare";
1630 $snap->{'snaptime'} = time();
1631 $snap->{'description'} = $comment if $comment;
1632 $conf->{snapshots
}->{$snapname} = $snap;
1634 write_config
($vmid, $conf);
1637 lock_container
($vmid, 10, $updatefn);
1642 my $snapshot_commit = sub {
1643 my ($vmid, $snapname) = @_;
1645 my $updatefn = sub {
1647 my $conf = load_config
($vmid);
1649 die "missing snapshot lock\n"
1650 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1652 die "snapshot '$snapname' does not exist\n"
1653 if !defined($conf->{snapshots
}->{$snapname});
1655 die "wrong snapshot state\n"
1656 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1657 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1659 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1660 delete $conf->{lock};
1661 $conf->{parent
} = $snapname;
1663 write_config
($vmid, $conf);
1666 lock_container
($vmid, 10 ,$updatefn);
1670 my ($feature, $conf, $storecfg, $snapname) = @_;
1674 foreach_mountpoint
($conf, sub {
1675 my ($ms, $mountpoint) = @_;
1677 return if $err; # skip further test
1679 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1681 # TODO: implement support for mountpoints
1682 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1686 return $err ?
0 : 1;
1689 sub snapshot_create
{
1690 my ($vmid, $snapname, $comment) = @_;
1692 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1694 my $conf = load_config
($vmid);
1696 my $running = check_running
($vmid);
1699 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1700 PVE
::Tools
::run_command
(['/bin/sync']);
1703 my $storecfg = PVE
::Storage
::config
();
1704 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1705 my $volid = $rootinfo->{volume
};
1708 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1711 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1712 &$snapshot_commit($vmid, $snapname);
1715 snapshot_delete
($vmid, $snapname, 1);
1720 sub snapshot_delete
{
1721 my ($vmid, $snapname, $force) = @_;
1727 my $updatefn = sub {
1729 $conf = load_config
($vmid);
1731 die "you can't delete a snapshot if vm is a template\n"
1732 if is_template
($conf);
1734 $snap = $conf->{snapshots
}->{$snapname};
1738 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1740 $snap->{snapstate
} = 'delete';
1742 write_config
($vmid, $conf);
1745 lock_container
($vmid, 10, $updatefn);
1747 my $storecfg = PVE
::Storage
::config
();
1749 my $del_snap = sub {
1753 if ($conf->{parent
} eq $snapname) {
1754 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1755 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1757 delete $conf->{parent
};
1761 delete $conf->{snapshots
}->{$snapname};
1763 write_config
($vmid, $conf);
1766 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1767 my $rootinfo = parse_ct_mountpoint
($rootfs);
1768 my $volid = $rootinfo->{volume
};
1771 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1775 if(!$err || ($err && $force)) {
1776 lock_container
($vmid, 10, $del_snap);
1778 die "Can't delete snapshot: $vmid $snapname $err\n";
1783 sub snapshot_rollback
{
1784 my ($vmid, $snapname) = @_;
1786 my $storecfg = PVE
::Storage
::config
();
1788 my $conf = load_config
($vmid);
1790 die "you can't rollback if vm is a template\n" if is_template
($conf);
1792 my $snap = $conf->{snapshots
}->{$snapname};
1794 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1796 my $rootfs = $snap->{rootfs
};
1797 my $rootinfo = parse_ct_mountpoint
($rootfs);
1798 my $volid = $rootinfo->{volume
};
1800 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1802 my $updatefn = sub {
1804 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1805 if $snap->{snapstate
};
1809 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1811 die "unable to rollback vm $vmid: vm is running\n"
1812 if check_running
($vmid);
1814 $conf->{lock} = 'rollback';
1818 # copy snapshot config to current config
1820 my $tmp_conf = $conf;
1821 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1822 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1823 delete $conf->{snaptime
};
1824 delete $conf->{snapname
};
1825 $conf->{parent
} = $snapname;
1827 write_config
($vmid, $conf);
1830 my $unlockfn = sub {
1831 delete $conf->{lock};
1832 write_config
($vmid, $conf);
1835 lock_container
($vmid, 10, $updatefn);
1837 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1839 lock_container
($vmid, 5, $unlockfn);
1842 sub template_create
{
1843 my ($vmid, $conf) = @_;
1845 my $storecfg = PVE
::Storage
::config
();
1847 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1848 my $volid = $rootinfo->{volume
};
1850 die "Template feature is not available for '$volid'\n"
1851 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1853 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1855 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1856 $rootinfo->{volume
} = $template_volid;
1857 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1859 write_config
($vmid, $conf);
1865 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1868 sub mountpoint_names
{
1871 my @names = ('rootfs');
1873 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1874 push @names, "mp$i";
1877 return $reverse ?
reverse @names : @names;
1880 # The container might have *different* symlinks than the host. realpath/abs_path
1881 # use the actual filesystem to resolve links.
1882 sub sanitize_mountpoint
{
1884 $mp = '/' . $mp; # we always start with a slash
1885 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1886 $mp =~ s
@/\./@@g; # collapse /./
1887 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1888 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1889 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1893 sub foreach_mountpoint_full
{
1894 my ($conf, $reverse, $func) = @_;
1896 foreach my $key (mountpoint_names
($reverse)) {
1897 my $value = $conf->{$key};
1898 next if !defined($value);
1899 my $mountpoint = parse_ct_mountpoint
($value, 1);
1900 next if !defined($mountpoint);
1902 # just to be sure: rootfs is /
1903 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1904 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1906 $path = $mountpoint->{volume
};
1907 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1909 &$func($key, $mountpoint);
1913 sub foreach_mountpoint
{
1914 my ($conf, $func) = @_;
1916 foreach_mountpoint_full
($conf, 0, $func);
1919 sub foreach_mountpoint_reverse
{
1920 my ($conf, $func) = @_;
1922 foreach_mountpoint_full
($conf, 1, $func);
1925 sub check_ct_modify_config_perm
{
1926 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1928 return 1 if $authuser ne 'root@pam';
1930 foreach my $opt (@$key_list) {
1932 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1933 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1934 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1935 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1936 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1937 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1938 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1939 $opt eq 'searchdomain' || $opt eq 'hostname') {
1940 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1942 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1950 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1952 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1953 my $volid_list = get_vm_volumes
($conf);
1955 foreach_mountpoint_reverse
($conf, sub {
1956 my ($ms, $mountpoint) = @_;
1958 my $volid = $mountpoint->{volume
};
1959 my $mount = $mountpoint->{mp
};
1961 return if !$volid || !$mount;
1963 my $mount_path = "$rootdir/$mount";
1964 $mount_path =~ s!/+!/!g;
1966 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
1969 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
1982 my ($vmid, $storage_cfg, $conf) = @_;
1984 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1985 File
::Path
::make_path
($rootdir);
1987 my $volid_list = get_vm_volumes
($conf);
1988 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
1991 foreach_mountpoint
($conf, sub {
1992 my ($ms, $mountpoint) = @_;
1994 my $volid = $mountpoint->{volume
};
1995 my $mount = $mountpoint->{mp
};
1997 return if !$volid || !$mount;
1999 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2000 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2001 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2003 die "unable to mount base volume - internal error" if $isBase;
2005 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2009 warn "mounting container failed - $err";
2010 umount_all
($vmid, $storage_cfg, $conf, 1);
2017 sub mountpoint_mount_path
{
2018 my ($mountpoint, $storage_cfg, $snapname) = @_;
2020 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2023 my $check_mount_path = sub {
2025 $path = File
::Spec-
>canonpath($path);
2026 my $real = Cwd
::realpath
($path);
2027 if ($real ne $path) {
2028 die "mount path modified by symlink: $path != $real";
2032 # use $rootdir = undef to just return the corresponding mount path
2033 sub mountpoint_mount
{
2034 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2036 my $volid = $mountpoint->{volume
};
2037 my $mount = $mountpoint->{mp
};
2039 return if !$volid || !$mount;
2043 if (defined($rootdir)) {
2044 $rootdir =~ s!/+$!!;
2045 $mount_path = "$rootdir/$mount";
2046 $mount_path =~ s!/+!/!g;
2047 &$check_mount_path($mount_path);
2048 File
::Path
::mkpath
($mount_path);
2051 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2053 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2057 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2058 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2060 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2061 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2063 if ($format eq 'subvol') {
2066 if ($scfg->{type
} eq 'zfspool') {
2067 my $path_arg = $path;
2068 $path_arg =~ s!^/+!!;
2069 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2071 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2074 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2077 return wantarray ?
($path, 0) : $path;
2078 } elsif ($format eq 'raw') {
2079 my $use_loopdev = 0;
2081 if ($scfg->{path
}) {
2082 push @extra_opts, '-o', 'loop';
2084 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2087 die "unsupported storage type '$scfg->{type}'\n";
2090 if ($isBase || defined($snapname)) {
2091 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2093 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2096 return wantarray ?
($path, $use_loopdev) : $path;
2098 die "unsupported image format '$format'\n";
2100 } elsif ($volid =~ m
|^/dev/.+|) {
2101 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2102 return wantarray ?
($volid, 0) : $volid;
2103 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2104 &$check_mount_path($volid);
2105 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2106 return wantarray ?
($volid, 0) : $volid;
2109 die "unsupported storage";
2112 sub get_vm_volumes
{
2113 my ($conf, $excludes) = @_;
2117 foreach_mountpoint
($conf, sub {
2118 my ($ms, $mountpoint) = @_;
2120 return if $excludes && $ms eq $excludes;
2122 my $volid = $mountpoint->{volume
};
2124 return if !$volid || $volid =~ m
|^/|;
2126 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2129 push @$vollist, $volid;
2138 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2142 my ($storage_cfg, $volid) = @_;
2144 if ($volid =~ m!^/dev/.+!) {
2149 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2151 die "cannot format volume '$volid' with no storage\n" if !$storage;
2153 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2155 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2157 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2158 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2160 die "cannot format volume '$volid' (format == $format)\n"
2161 if $format ne 'raw';
2167 my ($storecfg, $vollist) = @_;
2169 foreach my $volid (@$vollist) {
2170 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2176 my ($storecfg, $vmid, $settings, $conf) = @_;
2181 foreach_mountpoint
($settings, sub {
2182 my ($ms, $mountpoint) = @_;
2184 my $volid = $mountpoint->{volume
};
2185 my $mp = $mountpoint->{mp
};
2187 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2189 return if !$storage;
2191 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2192 my ($storeid, $size_gb) = ($1, $2);
2194 my $size_kb = int(${size_gb
}*1024) * 1024;
2196 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2197 # fixme: use better naming ct-$vmid-disk-X.raw?
2199 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2201 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2203 format_disk
($storecfg, $volid);
2205 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2208 } elsif ($scfg->{type
} eq 'zfspool') {
2210 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2212 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2214 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2215 format_disk
($storecfg, $volid);
2217 } elsif ($scfg->{type
} eq 'rbd') {
2219 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2220 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2221 format_disk
($storecfg, $volid);
2223 die "unable to create containers on storage type '$scfg->{type}'\n";
2225 push @$vollist, $volid;
2226 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2227 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2229 # use specified/existing volid
2233 # free allocated images on error
2235 destroy_disks
($storecfg, $vollist);
2241 # bash completion helper
2243 sub complete_os_templates
{
2244 my ($cmdname, $pname, $cvalue) = @_;
2246 my $cfg = PVE
::Storage
::config
();
2250 if ($cvalue =~ m/^([^:]+):/) {
2254 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2255 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2258 foreach my $id (keys %$data) {
2259 foreach my $item (@{$data->{$id}}) {
2260 push @$res, $item->{volid
} if defined($item->{volid
});
2267 my $complete_ctid_full = sub {
2270 my $idlist = vmstatus
();
2272 my $active_hash = list_active_containers
();
2276 foreach my $id (keys %$idlist) {
2277 my $d = $idlist->{$id};
2278 if (defined($running)) {
2279 next if $d->{template
};
2280 next if $running && !$active_hash->{$id};
2281 next if !$running && $active_hash->{$id};
2290 return &$complete_ctid_full();
2293 sub complete_ctid_stopped
{
2294 return &$complete_ctid_full(0);
2297 sub complete_ctid_running
{
2298 return &$complete_ctid_full(1);