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
;
21 use Time
::HiRes qw
(gettimeofday
);
25 my $nodename = PVE
::INotify
::nodename
();
27 my $cpuinfo= PVE
::ProcFSTools
::read_cpuinfo
();
29 our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
31 '--xattrs-include=user.*',
32 '--xattrs-include=security.capability',
33 '--warning=no-xattr-write' ];
35 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
41 format_description
=> 'volume',
42 description
=> 'Volume, device or directory to mount into the container.',
46 format_description
=> '[1|0]',
47 description
=> 'Whether to include the mountpoint in backups.',
52 format
=> 'disk-size',
53 format_description
=> 'DiskSize',
54 description
=> 'Volume size (read only value).',
59 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
60 type
=> 'string', format
=> $rootfs_desc,
61 description
=> "Use volume as container root.",
65 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
66 description
=> "The name of the snapshot.",
67 type
=> 'string', format
=> 'pve-configid',
75 description
=> "Lock/unlock the VM.",
76 enum
=> [qw(migrate backup snapshot rollback)],
81 description
=> "Specifies whether a VM will be started during system bootup.",
84 startup
=> get_standard_option
('pve-startup-order'),
88 description
=> "Enable/disable Template.",
94 enum
=> ['amd64', 'i386'],
95 description
=> "OS architecture type.",
101 enum
=> ['debian', 'ubuntu', 'centos', 'fedora', 'archlinux'],
102 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
107 description
=> "Attach a console device (/dev/console) to the container.",
113 description
=> "Specify the number of tty available to the container",
121 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.",
129 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.",
137 description
=> "Amount of RAM for the VM in MB.",
144 description
=> "Amount of SWAP for the VM in MB.",
150 description
=> "Set a host name for the container.",
151 type
=> 'string', format
=> 'dns-name',
157 description
=> "Container description. Only used on the configuration web interface.",
161 type
=> 'string', format
=> 'dns-name-list',
162 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
166 type
=> 'string', format
=> 'address-list',
167 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.",
169 rootfs
=> get_standard_option
('pve-ct-rootfs'),
172 type
=> 'string', format
=> 'pve-configid',
174 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
178 description
=> "Timestamp for snapshots.",
184 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).",
186 enum
=> ['shell', 'console', 'tty'],
192 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.",
198 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
203 my $valid_lxc_conf_keys = {
207 'lxc.haltsignal' => 1,
208 'lxc.rebootsignal' => 1,
209 'lxc.stopsignal' => 1,
211 'lxc.network.type' => 1,
212 'lxc.network.flags' => 1,
213 'lxc.network.link' => 1,
214 'lxc.network.mtu' => 1,
215 'lxc.network.name' => 1,
216 'lxc.network.hwaddr' => 1,
217 'lxc.network.ipv4' => 1,
218 'lxc.network.ipv4.gateway' => 1,
219 'lxc.network.ipv6' => 1,
220 'lxc.network.ipv6.gateway' => 1,
221 'lxc.network.script.up' => 1,
222 'lxc.network.script.down' => 1,
224 'lxc.console.logfile' => 1,
227 'lxc.devttydir' => 1,
228 'lxc.hook.autodev' => 1,
232 'lxc.mount.entry' => 1,
233 'lxc.mount.auto' => 1,
235 'lxc.rootfs.mount' => 1,
236 'lxc.rootfs.options' => 1,
240 'lxc.aa_profile' => 1,
241 'lxc.aa_allow_incomplete' => 1,
242 'lxc.se_context' => 1,
245 'lxc.hook.pre-start' => 1,
246 'lxc.hook.pre-mount' => 1,
247 'lxc.hook.mount' => 1,
248 'lxc.hook.start' => 1,
249 'lxc.hook.stop' => 1,
250 'lxc.hook.post-stop' => 1,
251 'lxc.hook.clone' => 1,
252 'lxc.hook.destroy' => 1,
255 'lxc.start.auto' => 1,
256 'lxc.start.delay' => 1,
257 'lxc.start.order' => 1,
259 'lxc.environment' => 1,
270 description
=> "Network interface type.",
275 format_description
=> 'String',
276 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
277 pattern
=> '[-_.\w\d]+',
281 format_description
=> 'vmbr<Number>',
282 description
=> 'Bridge to attach the network device to.',
283 pattern
=> '[-_.\w\d]+',
288 format_description
=> 'MAC',
289 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
290 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
295 format_description
=> 'Number',
296 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
297 minimum
=> 64, # minimum ethernet frame is 64 bytes
302 format
=> 'pve-ipv4-config',
303 format_description
=> 'IPv4Format/CIDR',
304 description
=> 'IPv4 address in CIDR format.',
310 format_description
=> 'GatewayIPv4',
311 description
=> 'Default gateway for IPv4 traffic.',
316 format
=> 'pve-ipv6-config',
317 format_description
=> 'IPv6Format/CIDR',
318 description
=> 'IPv6 address in CIDR format.',
324 format_description
=> 'GatewayIPv6',
325 description
=> 'Default gateway for IPv6 traffic.',
330 format_description
=> '[1|0]',
331 description
=> "Controls whether this interface's firewall rules should be used.",
336 format_description
=> 'VlanNo',
339 description
=> "VLAN tag foro this interface.",
343 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
345 my $MAX_LXC_NETWORKS = 10;
346 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
347 $confdesc->{"net$i"} = {
349 type
=> 'string', format
=> $netconf_desc,
350 description
=> "Specifies network interfaces for the container.",
358 format_description
=> 'Path',
359 description
=> 'Path to the mountpoint as seen from inside the container.',
363 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
367 type
=> 'string', format
=> 'pve-volume-id',
368 description
=> "Reference to unused volumes.",
371 my $MAX_MOUNT_POINTS = 10;
372 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
373 $confdesc->{"mp$i"} = {
375 type
=> 'string', format
=> $mp_desc,
376 description
=> "Use volume as container mount point (experimental feature).",
381 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
382 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
383 $confdesc->{"unused$i"} = $unuseddesc;
386 sub write_pct_config
{
387 my ($filename, $conf) = @_;
389 delete $conf->{snapstate
}; # just to be sure
391 my $generate_raw_config = sub {
396 # add description as comment to top of file
397 my $descr = $conf->{description
} || '';
398 foreach my $cl (split(/\n/, $descr)) {
399 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
402 foreach my $key (sort keys %$conf) {
403 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
404 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
405 my $value = $conf->{$key};
406 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
407 $raw .= "$key: $value\n";
410 if (my $lxcconf = $conf->{lxc
}) {
411 foreach my $entry (@$lxcconf) {
412 my ($k, $v) = @$entry;
420 my $raw = &$generate_raw_config($conf);
422 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
423 $raw .= "\n[$snapname]\n";
424 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
431 my ($key, $value) = @_;
433 die "unknown setting '$key'\n" if !$confdesc->{$key};
435 my $type = $confdesc->{$key}->{type
};
437 if (!defined($value)) {
438 die "got undefined value\n";
441 if ($value =~ m/[\n\r]/) {
442 die "property contains a line feed\n";
445 if ($type eq 'boolean') {
446 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
447 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
448 die "type check ('boolean') failed - got '$value'\n";
449 } elsif ($type eq 'integer') {
450 return int($1) if $value =~ m/^(\d+)$/;
451 die "type check ('integer') failed - got '$value'\n";
452 } elsif ($type eq 'number') {
453 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
454 die "type check ('number') failed - got '$value'\n";
455 } elsif ($type eq 'string') {
456 if (my $fmt = $confdesc->{$key}->{format
}) {
457 PVE
::JSONSchema
::check_format
($fmt, $value);
466 sub parse_pct_config
{
467 my ($filename, $raw) = @_;
469 return undef if !defined($raw);
472 digest
=> Digest
::SHA
::sha1_hex
($raw),
476 $filename =~ m
|/lxc/(\d
+).conf
$|
477 || die "got strange filename '$filename'";
485 my @lines = split(/\n/, $raw);
486 foreach my $line (@lines) {
487 next if $line =~ m/^\s*$/;
489 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
491 $conf->{description
} = $descr if $descr;
493 $conf = $res->{snapshots
}->{$section} = {};
497 if ($line =~ m/^\#(.*)\s*$/) {
498 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
502 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
505 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
506 push @{$conf->{lxc
}}, [$key, $value];
508 warn "vm $vmid - unable to parse config: $line\n";
510 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
511 $descr .= PVE
::Tools
::decode_text
($2);
512 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
513 $conf->{snapstate
} = $1;
514 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
517 eval { $value = check_type
($key, $value); };
518 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
519 $conf->{$key} = $value;
521 warn "vm $vmid - unable to parse config: $line\n";
525 $conf->{description
} = $descr if $descr;
527 delete $res->{snapstate
}; # just to be sure
533 my $vmlist = PVE
::Cluster
::get_vmlist
();
535 return $res if !$vmlist || !$vmlist->{ids
};
536 my $ids = $vmlist->{ids
};
538 foreach my $vmid (keys %$ids) {
539 next if !$vmid; # skip CT0
540 my $d = $ids->{$vmid};
541 next if !$d->{node
} || $d->{node
} ne $nodename;
542 next if !$d->{type
} || $d->{type
} ne 'lxc';
543 $res->{$vmid}->{type
} = 'lxc';
548 sub cfs_config_path
{
549 my ($vmid, $node) = @_;
551 $node = $nodename if !$node;
552 return "nodes/$node/lxc/$vmid.conf";
556 my ($vmid, $node) = @_;
558 my $cfspath = cfs_config_path
($vmid, $node);
559 return "/etc/pve/$cfspath";
563 my ($vmid, $node) = @_;
565 $node = $nodename if !$node;
566 my $cfspath = cfs_config_path
($vmid, $node);
568 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
569 die "container $vmid does not exists\n" if !defined($conf);
575 my ($vmid, $conf) = @_;
577 my $dir = "/etc/pve/nodes/$nodename/lxc";
580 write_config
($vmid, $conf);
586 unlink config_file
($vmid, $nodename);
590 my ($vmid, $conf) = @_;
592 my $cfspath = cfs_config_path
($vmid);
594 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
597 # flock: we use one file handle per process, so lock file
598 # can be called multiple times and succeeds for the same process.
600 my $lock_handles = {};
601 my $lockdir = "/run/lock/lxc";
606 return "$lockdir/pve-config-${vmid}.lock";
610 my ($vmid, $timeout) = @_;
612 $timeout = 10 if !$timeout;
615 my $filename = lock_filename
($vmid);
617 mkdir $lockdir if !-d
$lockdir;
619 my $lock_func = sub {
620 if (!$lock_handles->{$$}->{$filename}) {
621 my $fh = new IO
::File
(">>$filename") ||
622 die "can't open file - $!\n";
623 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
626 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
627 print STDERR
"trying to aquire lock...";
630 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
631 # try again on EINTR (see bug #273)
632 if ($success || ($! != EINTR
)) {
637 print STDERR
" failed\n";
638 die "can't aquire lock - $!\n";
641 print STDERR
" OK\n";
644 $lock_handles->{$$}->{$filename}->{refcount
}++;
647 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
650 die "can't lock file '$filename' - $err";
657 my $filename = lock_filename
($vmid);
659 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
660 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
661 if ($refcount <= 0) {
662 $lock_handles->{$$}->{$filename} = undef;
669 my ($vmid, $timeout, $code, @param) = @_;
673 lock_aquire
($vmid, $timeout);
674 eval { $res = &$code(@param) };
686 return defined($confdesc->{$name});
689 # add JSON properties for create and set function
690 sub json_config_properties
{
693 foreach my $opt (keys %$confdesc) {
694 next if $opt eq 'parent' || $opt eq 'snaptime';
695 next if $prop->{$opt};
696 $prop->{$opt} = $confdesc->{$opt};
702 sub json_config_properties_no_rootfs
{
705 foreach my $opt (keys %$confdesc) {
706 next if $prop->{$opt};
707 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
708 $prop->{$opt} = $confdesc->{$opt};
714 # container status helpers
716 sub list_active_containers
{
718 my $filename = "/proc/net/unix";
720 # similar test is used by lcxcontainers.c: list_active_containers
723 my $fh = IO
::File-
>new ($filename, "r");
726 while (defined(my $line = <$fh>)) {
727 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
729 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
740 # warning: this is slow
744 my $active_hash = list_active_containers
();
746 return 1 if defined($active_hash->{$vmid});
751 sub get_container_disk_usage
{
752 my ($vmid, $pid) = @_;
754 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
757 my $last_proc_vmid_stat;
759 my $parse_cpuacct_stat = sub {
762 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
766 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
779 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
781 my $active_hash = list_active_containers
();
783 my $cpucount = $cpuinfo->{cpus
} || 1;
785 my $cdtime = gettimeofday
;
787 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
789 foreach my $vmid (keys %$list) {
790 my $d = $list->{$vmid};
792 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
793 warn $@ if $@; # ignore errors (consider them stopped)
795 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
797 my $cfspath = cfs_config_path
($vmid);
798 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
800 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
801 $d->{name
} =~ s/[\s]//g;
803 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
806 my $res = get_container_disk_usage
($vmid, $d->{pid
});
807 $d->{disk
} = $res->{used
};
808 $d->{maxdisk
} = $res->{total
};
811 # use 4GB by default ??
812 if (my $rootfs = $conf->{rootfs
}) {
813 my $rootinfo = parse_ct_mountpoint
($rootfs);
814 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
816 $d->{maxdisk
} = 4*1024*1024*1024;
822 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
823 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
834 $d->{template
} = is_template
($conf);
837 foreach my $vmid (keys %$list) {
838 my $d = $list->{$vmid};
841 next if !$pid; # skip stopped CTs
843 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
844 $d->{uptime
} = time - $ctime; # the method lxcfs uses
846 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
847 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
849 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
850 my @bytes = split(/\n/, $blkio_bytes);
851 foreach my $byte (@bytes) {
852 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
853 $d->{diskread
} = $2 if $key eq 'Read';
854 $d->{diskwrite
} = $2 if $key eq 'Write';
858 my $pstat = &$parse_cpuacct_stat($vmid);
860 my $used = $pstat->{utime} + $pstat->{stime
};
862 my $old = $last_proc_vmid_stat->{$vmid};
864 $last_proc_vmid_stat->{$vmid} = {
872 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
875 my $dutime = $used - $old->{used
};
877 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
878 $last_proc_vmid_stat->{$vmid} = {
884 $d->{cpu
} = $old->{cpu
};
888 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
890 foreach my $dev (keys %$netdev) {
891 next if $dev !~ m/^veth([1-9]\d*)i/;
893 my $d = $list->{$vmid};
897 $d->{netout
} += $netdev->{$dev}->{receive
};
898 $d->{netin
} += $netdev->{$dev}->{transmit
};
905 sub classify_mountpoint
{
908 return 'device' if $vol =~ m!^/dev/!;
914 sub parse_ct_mountpoint
{
915 my ($data, $noerr) = @_;
920 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
922 return undef if $noerr;
926 if (defined(my $size = $res->{size
})) {
927 $size = PVE
::JSONSchema
::parse_size
($size);
928 if (!defined($size)) {
929 return undef if $noerr;
930 die "invalid size: $size\n";
932 $res->{size
} = $size;
935 $res->{type
} = classify_mountpoint
($res->{volume
});
940 sub print_ct_mountpoint
{
941 my ($info, $nomp) = @_;
942 my $skip = [ 'type' ];
943 push @$skip, 'mp' if $nomp;
944 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
947 sub print_lxc_network
{
949 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
952 sub parse_lxc_network
{
957 return $res if !$data;
959 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
961 $res->{type
} = 'veth';
962 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
967 sub read_cgroup_value
{
968 my ($group, $vmid, $name, $full) = @_;
970 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
972 return PVE
::Tools
::file_get_contents
($path) if $full;
974 return PVE
::Tools
::file_read_firstline
($path);
977 sub write_cgroup_value
{
978 my ($group, $vmid, $name, $value) = @_;
980 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
981 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
985 sub find_lxc_console_pids
{
989 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
992 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
995 my @args = split(/\0/, $cmdline);
997 # serach for lxc-console -n <vmid>
998 return if scalar(@args) != 3;
999 return if $args[1] ne '-n';
1000 return if $args[2] !~ m/^\d+$/;
1001 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
1003 my $vmid = $args[2];
1005 push @{$res->{$vmid}}, $pid;
1017 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1019 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1021 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1026 # Note: we cannot use Net:IP, because that only allows strict
1028 sub parse_ipv4_cidr
{
1029 my ($cidr, $noerr) = @_;
1031 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1032 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1035 return undef if $noerr;
1037 die "unable to parse ipv4 address/mask\n";
1043 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1046 sub check_protection
{
1047 my ($vm_conf, $err_msg) = @_;
1049 if ($vm_conf->{protection
}) {
1050 die "$err_msg - protection mode enabled\n";
1054 sub update_lxc_config
{
1055 my ($storage_cfg, $vmid, $conf) = @_;
1057 my $dir = "/var/lib/lxc/$vmid";
1059 if ($conf->{template
}) {
1061 unlink "$dir/config";
1068 die "missing 'arch' - internal error" if !$conf->{arch
};
1069 $raw .= "lxc.arch = $conf->{arch}\n";
1071 my $unprivileged = $conf->{unprivileged
};
1072 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1074 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1075 if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | archlinux)$/x) {
1076 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1077 if ($unprivileged || $custom_idmap) {
1078 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
1084 $raw .= "lxc.monitor.unshare = 1\n";
1086 # Should we read them from /etc/subuid?
1087 if ($unprivileged && !$custom_idmap) {
1088 $raw .= "lxc.id_map = u 0 100000 65536\n";
1089 $raw .= "lxc.id_map = g 0 100000 65536\n";
1092 if (!has_dev_console
($conf)) {
1093 $raw .= "lxc.console = none\n";
1094 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1097 my $ttycount = get_tty_count
($conf);
1098 $raw .= "lxc.tty = $ttycount\n";
1100 # some init scripts expects a linux terminal (turnkey).
1101 $raw .= "lxc.environment = TERM=linux\n";
1103 my $utsname = $conf->{hostname
} || "CT$vmid";
1104 $raw .= "lxc.utsname = $utsname\n";
1106 my $memory = $conf->{memory
} || 512;
1107 my $swap = $conf->{swap
} // 0;
1109 my $lxcmem = int($memory*1024*1024);
1110 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1111 $raw .= "lxc.cgroup.memory.kmem.limit_in_bytes = $lxcmem\n";
1113 my $lxcswap = int(($memory + $swap)*1024*1024);
1114 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1116 if (my $cpulimit = $conf->{cpulimit
}) {
1117 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1118 my $value = int(100000*$cpulimit);
1119 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1122 my $shares = $conf->{cpuunits
} || 1024;
1123 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1125 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1126 $mountpoint->{mp
} = '/';
1128 $raw .= "lxc.rootfs = $dir/rootfs\n";
1129 $raw .= "lxc.hook.stop = /usr/lib/x86_64-linux-gnu/lxc/hooks/unmount-namespace\n";
1132 foreach my $k (keys %$conf) {
1133 next if $k !~ m/^net(\d+)$/;
1135 my $d = parse_lxc_network
($conf->{$k});
1137 $raw .= "lxc.network.type = veth\n";
1138 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1139 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1140 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1141 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1144 if (my $lxcconf = $conf->{lxc
}) {
1145 foreach my $entry (@$lxcconf) {
1146 my ($k, $v) = @$entry;
1147 $netcount++ if $k eq 'lxc.network.type';
1148 $raw .= "$k = $v\n";
1152 $raw .= "lxc.network.type = empty\n" if !$netcount;
1154 File
::Path
::mkpath
("$dir/rootfs");
1156 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1159 # verify and cleanup nameserver list (replace \0 with ' ')
1160 sub verify_nameserver_list
{
1161 my ($nameserver_list) = @_;
1164 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1165 PVE
::JSONSchema
::pve_verify_ip
($server);
1166 push @list, $server;
1169 return join(' ', @list);
1172 sub verify_searchdomain_list
{
1173 my ($searchdomain_list) = @_;
1176 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1177 # todo: should we add checks for valid dns domains?
1178 push @list, $server;
1181 return join(' ', @list);
1184 sub add_unused_volume
{
1185 my ($config, $volid) = @_;
1188 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1189 my $test = "unused$ind";
1190 if (my $vid = $config->{$test}) {
1191 return if $vid eq $volid; # do not add duplicates
1197 die "To many unused volume - please delete them first.\n" if !$key;
1199 $config->{$key} = $volid;
1204 sub update_pct_config
{
1205 my ($vmid, $conf, $running, $param, $delete) = @_;
1210 my @deleted_volumes;
1214 my $pid = find_lxc_pid
($vmid);
1215 $rootdir = "/proc/$pid/root";
1218 my $hotplug_error = sub {
1220 push @nohotplug, @_;
1227 if (defined($delete)) {
1228 foreach my $opt (@$delete) {
1229 if (!exists($conf->{$opt})) {
1230 warn "no such option: $opt\n";
1234 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1235 die "unable to delete required option '$opt'\n";
1236 } elsif ($opt eq 'swap') {
1237 delete $conf->{$opt};
1238 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1239 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1240 delete $conf->{$opt};
1241 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1242 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1243 next if $hotplug_error->($opt);
1244 delete $conf->{$opt};
1245 } elsif ($opt =~ m/^net(\d)$/) {
1246 delete $conf->{$opt};
1249 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1250 } elsif ($opt eq 'protection') {
1251 delete $conf->{$opt};
1252 } elsif ($opt =~ m/^unused(\d+)$/) {
1253 next if $hotplug_error->($opt);
1254 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1255 push @deleted_volumes, $conf->{$opt};
1256 delete $conf->{$opt};
1257 } elsif ($opt =~ m/^mp(\d+)$/) {
1258 next if $hotplug_error->($opt);
1259 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1260 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1261 if ($mountpoint->{type
} eq 'volume') {
1262 add_unused_volume
($conf, $mountpoint->{volume
})
1264 delete $conf->{$opt};
1265 } elsif ($opt eq 'unprivileged') {
1266 die "unable to delete read-only option: '$opt'\n";
1270 write_config
($vmid, $conf) if $running;
1274 # There's no separate swap size to configure, there's memory and "total"
1275 # memory (iow. memory+swap). This means we have to change them together.
1276 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1277 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1278 if (defined($wanted_memory) || defined($wanted_swap)) {
1280 $wanted_memory //= ($conf->{memory
} || 512);
1281 $wanted_swap //= ($conf->{swap
} || 0);
1283 my $total = $wanted_memory + $wanted_swap;
1285 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1286 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1288 $conf->{memory
} = $wanted_memory;
1289 $conf->{swap
} = $wanted_swap;
1291 write_config
($vmid, $conf) if $running;
1294 foreach my $opt (keys %$param) {
1295 my $value = $param->{$opt};
1296 if ($opt eq 'hostname') {
1297 $conf->{$opt} = $value;
1298 } elsif ($opt eq 'onboot') {
1299 $conf->{$opt} = $value ?
1 : 0;
1300 } elsif ($opt eq 'startup') {
1301 $conf->{$opt} = $value;
1302 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1303 next if $hotplug_error->($opt);
1304 $conf->{$opt} = $value;
1305 } elsif ($opt eq 'nameserver') {
1306 next if $hotplug_error->($opt);
1307 my $list = verify_nameserver_list
($value);
1308 $conf->{$opt} = $list;
1309 } elsif ($opt eq 'searchdomain') {
1310 next if $hotplug_error->($opt);
1311 my $list = verify_searchdomain_list
($value);
1312 $conf->{$opt} = $list;
1313 } elsif ($opt eq 'cpulimit') {
1314 next if $hotplug_error->($opt); # FIXME: hotplug
1315 $conf->{$opt} = $value;
1316 } elsif ($opt eq 'cpuunits') {
1317 $conf->{$opt} = $value;
1318 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1319 } elsif ($opt eq 'description') {
1320 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1321 } elsif ($opt =~ m/^net(\d+)$/) {
1323 my $net = parse_lxc_network
($value);
1325 $conf->{$opt} = print_lxc_network
($net);
1327 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1329 } elsif ($opt eq 'protection') {
1330 $conf->{$opt} = $value ?
1 : 0;
1331 } elsif ($opt =~ m/^mp(\d+)$/) {
1332 next if $hotplug_error->($opt);
1333 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1334 $conf->{$opt} = $value;
1336 } elsif ($opt eq 'rootfs') {
1337 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1338 die "implement me: $opt";
1339 } elsif ($opt eq 'unprivileged') {
1340 die "unable to modify read-only option: '$opt'\n";
1342 die "implement me: $opt";
1344 write_config
($vmid, $conf) if $running;
1347 if (@deleted_volumes) {
1348 my $storage_cfg = PVE
::Storage
::config
();
1349 foreach my $volume (@deleted_volumes) {
1350 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1355 my $storage_cfg = PVE
::Storage
::config
();
1356 create_disks
($storage_cfg, $vmid, $conf, $conf);
1359 # This should be the last thing we do here
1360 if ($running && scalar(@nohotplug)) {
1361 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1365 sub has_dev_console
{
1368 return !(defined($conf->{console
}) && !$conf->{console
});
1374 return $conf->{tty
} // $confdesc->{tty
}->{default};
1380 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1383 sub get_console_command
{
1384 my ($vmid, $conf) = @_;
1386 my $cmode = get_cmode
($conf);
1388 if ($cmode eq 'console') {
1389 return ['lxc-console', '-n', $vmid, '-t', 0];
1390 } elsif ($cmode eq 'tty') {
1391 return ['lxc-console', '-n', $vmid];
1392 } elsif ($cmode eq 'shell') {
1393 return ['lxc-attach', '--clear-env', '-n', $vmid];
1395 die "internal error";
1399 sub get_primary_ips
{
1402 # return data from net0
1404 return undef if !defined($conf->{net0
});
1405 my $net = parse_lxc_network
($conf->{net0
});
1407 my $ipv4 = $net->{ip
};
1409 if ($ipv4 =~ /^(dhcp|manual)$/) {
1415 my $ipv6 = $net->{ip6
};
1417 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1424 return ($ipv4, $ipv6);
1427 sub delete_mountpoint_volume
{
1428 my ($storage_cfg, $vmid, $volume) = @_;
1430 return if classify_mountpoint
($volume) ne 'volume';
1432 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1433 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1436 sub destroy_lxc_container
{
1437 my ($storage_cfg, $vmid, $conf) = @_;
1439 foreach_mountpoint
($conf, sub {
1440 my ($ms, $mountpoint) = @_;
1441 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1444 rmdir "/var/lib/lxc/$vmid/rootfs";
1445 unlink "/var/lib/lxc/$vmid/config";
1446 rmdir "/var/lib/lxc/$vmid";
1447 destroy_config
($vmid);
1449 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1450 #PVE::Tools::run_command($cmd);
1453 sub vm_stop_cleanup
{
1454 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1459 my $vollist = get_vm_volumes
($conf);
1460 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1463 warn $@ if $@; # avoid errors - just warn
1466 my $safe_num_ne = sub {
1469 return 0 if !defined($a) && !defined($b);
1470 return 1 if !defined($a);
1471 return 1 if !defined($b);
1476 my $safe_string_ne = sub {
1479 return 0 if !defined($a) && !defined($b);
1480 return 1 if !defined($a);
1481 return 1 if !defined($b);
1487 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1489 if ($newnet->{type
} ne 'veth') {
1490 # for when there are physical interfaces
1491 die "cannot update interface of type $newnet->{type}";
1494 my $veth = "veth${vmid}i${netid}";
1495 my $eth = $newnet->{name
};
1497 if (my $oldnetcfg = $conf->{$opt}) {
1498 my $oldnet = parse_lxc_network
($oldnetcfg);
1500 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1501 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1503 PVE
::Network
::veth_delete
($veth);
1504 delete $conf->{$opt};
1505 write_config
($vmid, $conf);
1507 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1509 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1510 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1511 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1513 if ($oldnet->{bridge
}) {
1514 PVE
::Network
::tap_unplug
($veth);
1515 foreach (qw(bridge tag firewall)) {
1516 delete $oldnet->{$_};
1518 $conf->{$opt} = print_lxc_network
($oldnet);
1519 write_config
($vmid, $conf);
1522 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1523 foreach (qw(bridge tag firewall)) {
1524 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1526 $conf->{$opt} = print_lxc_network
($oldnet);
1527 write_config
($vmid, $conf);
1530 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1533 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1537 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1539 my $veth = "veth${vmid}i${netid}";
1540 my $vethpeer = $veth . "p";
1541 my $eth = $newnet->{name
};
1543 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1544 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1546 # attach peer in container
1547 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1548 PVE
::Tools
::run_command
($cmd);
1550 # link up peer in container
1551 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1552 PVE
::Tools
::run_command
($cmd);
1554 my $done = { type
=> 'veth' };
1555 foreach (qw(bridge tag firewall hwaddr name)) {
1556 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1558 $conf->{$opt} = print_lxc_network
($done);
1560 write_config
($vmid, $conf);
1563 sub update_ipconfig
{
1564 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1566 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1568 my $optdata = parse_lxc_network
($conf->{$opt});
1572 my $cmdargs = shift;
1573 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1575 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1577 my $change_ip_config = sub {
1578 my ($ipversion) = @_;
1580 my $family_opt = "-$ipversion";
1581 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1582 my $gw= "gw$suffix";
1583 my $ip= "ip$suffix";
1585 my $newip = $newnet->{$ip};
1586 my $newgw = $newnet->{$gw};
1587 my $oldip = $optdata->{$ip};
1589 my $change_ip = &$safe_string_ne($oldip, $newip);
1590 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1592 return if !$change_ip && !$change_gw;
1594 # step 1: add new IP, if this fails we cancel
1595 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1596 if ($change_ip && $is_real_ip) {
1597 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1604 # step 2: replace gateway
1605 # If this fails we delete the added IP and cancel.
1606 # If it succeeds we save the config and delete the old IP, ignoring
1607 # errors. The config is then saved.
1608 # Note: 'ip route replace' can add
1612 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1613 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1615 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1619 # the route was not replaced, the old IP is still available
1620 # rollback (delete new IP) and cancel
1622 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1623 warn $@ if $@; # no need to die here
1628 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1629 # if the route was not deleted, the guest might have deleted it manually
1635 # from this point on we save the configuration
1636 # step 3: delete old IP ignoring errors
1637 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1638 # We need to enable promote_secondaries, otherwise our newly added
1639 # address will be removed along with the old one.
1642 if ($ipversion == 4) {
1643 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1644 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1645 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1647 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1649 warn $@ if $@; # no need to die here
1651 if ($ipversion == 4) {
1652 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1656 foreach my $property ($ip, $gw) {
1657 if ($newnet->{$property}) {
1658 $optdata->{$property} = $newnet->{$property};
1660 delete $optdata->{$property};
1663 $conf->{$opt} = print_lxc_network
($optdata);
1664 write_config
($vmid, $conf);
1665 $lxc_setup->setup_network($conf);
1668 &$change_ip_config(4);
1669 &$change_ip_config(6);
1673 # Internal snapshots
1675 # NOTE: Snapshot create/delete involves several non-atomic
1676 # action, and can take a long time.
1677 # So we try to avoid locking the file and use 'lock' variable
1678 # inside the config file instead.
1680 my $snapshot_copy_config = sub {
1681 my ($source, $dest) = @_;
1683 foreach my $k (keys %$source) {
1684 next if $k eq 'snapshots';
1685 next if $k eq 'snapstate';
1686 next if $k eq 'snaptime';
1687 next if $k eq 'vmstate';
1688 next if $k eq 'lock';
1689 next if $k eq 'digest';
1690 next if $k eq 'description';
1692 $dest->{$k} = $source->{$k};
1696 my $snapshot_prepare = sub {
1697 my ($vmid, $snapname, $comment) = @_;
1701 my $updatefn = sub {
1703 my $conf = load_config
($vmid);
1705 die "you can't take a snapshot if it's a template\n"
1706 if is_template
($conf);
1710 $conf->{lock} = 'snapshot';
1712 die "snapshot name '$snapname' already used\n"
1713 if defined($conf->{snapshots
}->{$snapname});
1715 my $storecfg = PVE
::Storage
::config
();
1716 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1718 $snap = $conf->{snapshots
}->{$snapname} = {};
1720 &$snapshot_copy_config($conf, $snap);
1722 $snap->{'snapstate'} = "prepare";
1723 $snap->{'snaptime'} = time();
1724 $snap->{'description'} = $comment if $comment;
1725 $conf->{snapshots
}->{$snapname} = $snap;
1727 write_config
($vmid, $conf);
1730 lock_container
($vmid, 10, $updatefn);
1735 my $snapshot_commit = sub {
1736 my ($vmid, $snapname) = @_;
1738 my $updatefn = sub {
1740 my $conf = load_config
($vmid);
1742 die "missing snapshot lock\n"
1743 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1745 die "snapshot '$snapname' does not exist\n"
1746 if !defined($conf->{snapshots
}->{$snapname});
1748 die "wrong snapshot state\n"
1749 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1750 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1752 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1753 delete $conf->{lock};
1754 $conf->{parent
} = $snapname;
1756 write_config
($vmid, $conf);
1759 lock_container
($vmid, 10 ,$updatefn);
1763 my ($feature, $conf, $storecfg, $snapname) = @_;
1767 foreach_mountpoint
($conf, sub {
1768 my ($ms, $mountpoint) = @_;
1770 return if $err; # skip further test
1772 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1774 # TODO: implement support for mountpoints
1775 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1779 return $err ?
0 : 1;
1782 sub snapshot_create
{
1783 my ($vmid, $snapname, $comment) = @_;
1785 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1787 my $conf = load_config
($vmid);
1789 my $running = check_running
($vmid);
1792 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1793 PVE
::Tools
::run_command
(['/bin/sync']);
1796 my $storecfg = PVE
::Storage
::config
();
1797 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1798 my $volid = $rootinfo->{volume
};
1801 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1804 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1805 &$snapshot_commit($vmid, $snapname);
1808 snapshot_delete
($vmid, $snapname, 1);
1813 sub snapshot_delete
{
1814 my ($vmid, $snapname, $force) = @_;
1820 my $updatefn = sub {
1822 $conf = load_config
($vmid);
1824 die "you can't delete a snapshot if vm is a template\n"
1825 if is_template
($conf);
1827 $snap = $conf->{snapshots
}->{$snapname};
1831 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1833 $snap->{snapstate
} = 'delete';
1835 write_config
($vmid, $conf);
1838 lock_container
($vmid, 10, $updatefn);
1840 my $storecfg = PVE
::Storage
::config
();
1842 my $del_snap = sub {
1846 if ($conf->{parent
} eq $snapname) {
1847 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1848 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1850 delete $conf->{parent
};
1854 delete $conf->{snapshots
}->{$snapname};
1856 write_config
($vmid, $conf);
1859 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1860 my $rootinfo = parse_ct_mountpoint
($rootfs);
1861 my $volid = $rootinfo->{volume
};
1864 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1868 if(!$err || ($err && $force)) {
1869 lock_container
($vmid, 10, $del_snap);
1871 die "Can't delete snapshot: $vmid $snapname $err\n";
1876 sub snapshot_rollback
{
1877 my ($vmid, $snapname) = @_;
1879 my $storecfg = PVE
::Storage
::config
();
1881 my $conf = load_config
($vmid);
1883 die "you can't rollback if vm is a template\n" if is_template
($conf);
1885 my $snap = $conf->{snapshots
}->{$snapname};
1887 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1889 my $rootfs = $snap->{rootfs
};
1890 my $rootinfo = parse_ct_mountpoint
($rootfs);
1891 my $volid = $rootinfo->{volume
};
1893 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1895 my $updatefn = sub {
1897 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1898 if $snap->{snapstate
};
1902 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1904 die "unable to rollback vm $vmid: vm is running\n"
1905 if check_running
($vmid);
1907 $conf->{lock} = 'rollback';
1911 # copy snapshot config to current config
1913 my $tmp_conf = $conf;
1914 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1915 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1916 delete $conf->{snaptime
};
1917 delete $conf->{snapname
};
1918 $conf->{parent
} = $snapname;
1920 write_config
($vmid, $conf);
1923 my $unlockfn = sub {
1924 delete $conf->{lock};
1925 write_config
($vmid, $conf);
1928 lock_container
($vmid, 10, $updatefn);
1930 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1932 lock_container
($vmid, 5, $unlockfn);
1935 sub template_create
{
1936 my ($vmid, $conf) = @_;
1938 my $storecfg = PVE
::Storage
::config
();
1940 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1941 my $volid = $rootinfo->{volume
};
1943 die "Template feature is not available for '$volid'\n"
1944 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1946 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1948 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1949 $rootinfo->{volume
} = $template_volid;
1950 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1952 write_config
($vmid, $conf);
1958 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1961 sub mountpoint_names
{
1964 my @names = ('rootfs');
1966 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1967 push @names, "mp$i";
1970 return $reverse ?
reverse @names : @names;
1973 # The container might have *different* symlinks than the host. realpath/abs_path
1974 # use the actual filesystem to resolve links.
1975 sub sanitize_mountpoint
{
1977 $mp = '/' . $mp; # we always start with a slash
1978 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1979 $mp =~ s
@/\./@@g; # collapse /./
1980 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1981 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1982 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1986 sub foreach_mountpoint_full
{
1987 my ($conf, $reverse, $func) = @_;
1989 foreach my $key (mountpoint_names
($reverse)) {
1990 my $value = $conf->{$key};
1991 next if !defined($value);
1992 my $mountpoint = parse_ct_mountpoint
($value, 1);
1993 next if !defined($mountpoint);
1995 # just to be sure: rootfs is /
1996 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1997 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1999 $path = $mountpoint->{volume
};
2000 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
2002 &$func($key, $mountpoint);
2006 sub foreach_mountpoint
{
2007 my ($conf, $func) = @_;
2009 foreach_mountpoint_full
($conf, 0, $func);
2012 sub foreach_mountpoint_reverse
{
2013 my ($conf, $func) = @_;
2015 foreach_mountpoint_full
($conf, 1, $func);
2018 sub check_ct_modify_config_perm
{
2019 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2021 return 1 if $authuser ne 'root@pam';
2023 foreach my $opt (@$key_list) {
2025 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2026 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2027 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2028 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2029 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2030 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2031 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2032 $opt eq 'searchdomain' || $opt eq 'hostname') {
2033 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2035 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2043 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2045 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2046 my $volid_list = get_vm_volumes
($conf);
2048 foreach_mountpoint_reverse
($conf, sub {
2049 my ($ms, $mountpoint) = @_;
2051 my $volid = $mountpoint->{volume
};
2052 my $mount = $mountpoint->{mp
};
2054 return if !$volid || !$mount;
2056 my $mount_path = "$rootdir/$mount";
2057 $mount_path =~ s!/+!/!g;
2059 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2062 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2075 my ($vmid, $storage_cfg, $conf) = @_;
2077 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2078 File
::Path
::make_path
($rootdir);
2080 my $volid_list = get_vm_volumes
($conf);
2081 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2084 foreach_mountpoint
($conf, sub {
2085 my ($ms, $mountpoint) = @_;
2087 my $volid = $mountpoint->{volume
};
2088 my $mount = $mountpoint->{mp
};
2090 return if !$volid || !$mount;
2092 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2093 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2094 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2096 die "unable to mount base volume - internal error" if $isBase;
2098 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2102 warn "mounting container failed - $err";
2103 umount_all
($vmid, $storage_cfg, $conf, 1);
2110 sub mountpoint_mount_path
{
2111 my ($mountpoint, $storage_cfg, $snapname) = @_;
2113 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2116 my $check_mount_path = sub {
2118 $path = File
::Spec-
>canonpath($path);
2119 my $real = Cwd
::realpath
($path);
2120 if ($real ne $path) {
2121 die "mount path modified by symlink: $path != $real";
2130 if ($line =~ m
@^(/dev/loop\d
+):@) {
2134 my $cmd = ['losetup', '--associated', $path];
2135 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2139 # use $rootdir = undef to just return the corresponding mount path
2140 sub mountpoint_mount
{
2141 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2143 my $volid = $mountpoint->{volume
};
2144 my $mount = $mountpoint->{mp
};
2145 my $type = $mountpoint->{type
};
2147 return if !$volid || !$mount;
2151 if (defined($rootdir)) {
2152 $rootdir =~ s!/+$!!;
2153 $mount_path = "$rootdir/$mount";
2154 $mount_path =~ s!/+!/!g;
2155 &$check_mount_path($mount_path);
2156 File
::Path
::mkpath
($mount_path);
2159 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2161 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2165 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2166 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2168 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2169 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2171 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2173 if ($format eq 'subvol') {
2176 if ($scfg->{type
} ne 'zfspool') {
2177 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2180 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2182 return wantarray ?
($path, 0) : $path;
2183 } elsif ($format eq 'raw' || $format eq 'iso') {
2184 my $use_loopdev = 0;
2186 if ($scfg->{path
}) {
2187 push @extra_opts, '-o', 'loop';
2189 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2192 die "unsupported storage type '$scfg->{type}'\n";
2195 if ($format eq 'iso') {
2196 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2197 } elsif ($isBase || defined($snapname)) {
2198 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2200 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2203 return wantarray ?
($path, $use_loopdev) : $path;
2205 die "unsupported image format '$format'\n";
2207 } elsif ($type eq 'device') {
2208 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2209 return wantarray ?
($volid, 0) : $volid;
2210 } elsif ($type eq 'bind' && -d
$volid) {
2211 &$check_mount_path($volid);
2212 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2213 return wantarray ?
($volid, 0) : $volid;
2216 die "unsupported storage";
2219 sub get_vm_volumes
{
2220 my ($conf, $excludes) = @_;
2224 foreach_mountpoint
($conf, sub {
2225 my ($ms, $mountpoint) = @_;
2227 return if $excludes && $ms eq $excludes;
2229 my $volid = $mountpoint->{volume
};
2231 return if !$volid || $mountpoint->{type
} ne 'volume';
2233 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2236 push @$vollist, $volid;
2243 my ($dev, $rootuid, $rootgid) = @_;
2245 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2246 '-E', "root_owner=$rootuid:$rootgid",
2251 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2253 if ($volid =~ m!^/dev/.+!) {
2258 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2260 die "cannot format volume '$volid' with no storage\n" if !$storage;
2262 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2264 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2266 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2267 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2269 die "cannot format volume '$volid' (format == $format)\n"
2270 if $format ne 'raw';
2272 mkfs
($path, $rootuid, $rootgid);
2276 my ($storecfg, $vollist) = @_;
2278 foreach my $volid (@$vollist) {
2279 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2285 my ($storecfg, $vmid, $settings, $conf) = @_;
2290 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2291 my $chown_vollist = [];
2293 foreach_mountpoint
($settings, sub {
2294 my ($ms, $mountpoint) = @_;
2296 my $volid = $mountpoint->{volume
};
2297 my $mp = $mountpoint->{mp
};
2299 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2301 return if !$storage;
2303 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2304 my ($storeid, $size_gb) = ($1, $2);
2306 my $size_kb = int(${size_gb
}*1024) * 1024;
2308 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2309 # fixme: use better naming ct-$vmid-disk-X.raw?
2311 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2313 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2315 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2317 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2319 push @$chown_vollist, $volid;
2321 } elsif ($scfg->{type
} eq 'zfspool') {
2323 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2325 push @$chown_vollist, $volid;
2326 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2328 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2329 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2331 } elsif ($scfg->{type
} eq 'rbd') {
2333 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2334 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2335 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2337 die "unable to create containers on storage type '$scfg->{type}'\n";
2339 push @$vollist, $volid;
2340 $mountpoint->{volume
} = $volid;
2341 $mountpoint->{size
} = $size_kb * 1024;
2342 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2344 # use specified/existing volid
2348 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2349 foreach my $volid (@$chown_vollist) {
2350 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2351 chown($rootuid, $rootgid, $path);
2353 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2355 # free allocated images on error
2357 destroy_disks
($storecfg, $vollist);
2363 # bash completion helper
2365 sub complete_os_templates
{
2366 my ($cmdname, $pname, $cvalue) = @_;
2368 my $cfg = PVE
::Storage
::config
();
2372 if ($cvalue =~ m/^([^:]+):/) {
2376 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2377 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2380 foreach my $id (keys %$data) {
2381 foreach my $item (@{$data->{$id}}) {
2382 push @$res, $item->{volid
} if defined($item->{volid
});
2389 my $complete_ctid_full = sub {
2392 my $idlist = vmstatus
();
2394 my $active_hash = list_active_containers
();
2398 foreach my $id (keys %$idlist) {
2399 my $d = $idlist->{$id};
2400 if (defined($running)) {
2401 next if $d->{template
};
2402 next if $running && !$active_hash->{$id};
2403 next if !$running && $active_hash->{$id};
2412 return &$complete_ctid_full();
2415 sub complete_ctid_stopped
{
2416 return &$complete_ctid_full(0);
2419 sub complete_ctid_running
{
2420 return &$complete_ctid_full(1);
2430 my $lxc = $conf->{lxc
};
2431 foreach my $entry (@$lxc) {
2432 my ($key, $value) = @$entry;
2433 next if $key ne 'lxc.id_map';
2434 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2435 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2436 push @$id_map, [$type, $ct, $host, $length];
2438 $rootuid = $host if $type eq 'u';
2439 $rootgid = $host if $type eq 'g';
2442 die "failed to parse id_map: $value\n";
2446 if (!@$id_map && $conf->{unprivileged
}) {
2447 # Should we read them from /etc/subuid?
2448 $id_map = [ ['u', '0', '100000', '65536'],
2449 ['g', '0', '100000', '65536'] ];
2450 $rootuid = $rootgid = 100000;
2453 return ($id_map, $rootuid, $rootgid);
2456 sub userns_command
{
2459 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];