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', 'opensuse', '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 a 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 the 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 nor 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 nor 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 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,
266 description
=> "Network interface type.",
271 format_description
=> 'String',
272 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
273 pattern
=> '[-_.\w\d]+',
277 format_description
=> 'vmbr<Number>',
278 description
=> 'Bridge to attach the network device to.',
279 pattern
=> '[-_.\w\d]+',
284 format_description
=> 'MAC',
285 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
286 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
291 format_description
=> 'Number',
292 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
293 minimum
=> 64, # minimum ethernet frame is 64 bytes
298 format
=> 'pve-ipv4-config',
299 format_description
=> 'IPv4Format/CIDR',
300 description
=> 'IPv4 address in CIDR format.',
306 format_description
=> 'GatewayIPv4',
307 description
=> 'Default gateway for IPv4 traffic.',
312 format
=> 'pve-ipv6-config',
313 format_description
=> 'IPv6Format/CIDR',
314 description
=> 'IPv6 address in CIDR format.',
320 format_description
=> 'GatewayIPv6',
321 description
=> 'Default gateway for IPv6 traffic.',
326 format_description
=> '[1|0]',
327 description
=> "Controls whether this interface's firewall rules should be used.",
332 format_description
=> 'VlanNo',
335 description
=> "VLAN tag for this interface.",
340 pattern
=> qr/\d+(?:;\d+)*/,
341 format_description
=> 'vlanid[;vlanid...]',
342 description
=> "VLAN ids to pass through the interface",
346 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
348 my $MAX_LXC_NETWORKS = 10;
349 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
350 $confdesc->{"net$i"} = {
352 type
=> 'string', format
=> $netconf_desc,
353 description
=> "Specifies network interfaces for the container.",
361 format_description
=> 'Path',
362 description
=> 'Path to the mountpoint as seen from inside the container.',
365 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
369 type
=> 'string', format
=> 'pve-volume-id',
370 description
=> "Reference to unused volumes.",
373 my $MAX_MOUNT_POINTS = 10;
374 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
375 $confdesc->{"mp$i"} = {
377 type
=> 'string', format
=> $mp_desc,
378 description
=> "Use volume as container mount point (experimental feature).",
383 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
384 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
385 $confdesc->{"unused$i"} = $unuseddesc;
388 sub write_pct_config
{
389 my ($filename, $conf) = @_;
391 delete $conf->{snapstate
}; # just to be sure
393 my $generate_raw_config = sub {
398 # add description as comment to top of file
399 my $descr = $conf->{description
} || '';
400 foreach my $cl (split(/\n/, $descr)) {
401 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
404 foreach my $key (sort keys %$conf) {
405 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
406 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
407 my $value = $conf->{$key};
408 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
409 $raw .= "$key: $value\n";
412 if (my $lxcconf = $conf->{lxc
}) {
413 foreach my $entry (@$lxcconf) {
414 my ($k, $v) = @$entry;
422 my $raw = &$generate_raw_config($conf);
424 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
425 $raw .= "\n[$snapname]\n";
426 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
433 my ($key, $value) = @_;
435 die "unknown setting '$key'\n" if !$confdesc->{$key};
437 my $type = $confdesc->{$key}->{type
};
439 if (!defined($value)) {
440 die "got undefined value\n";
443 if ($value =~ m/[\n\r]/) {
444 die "property contains a line feed\n";
447 if ($type eq 'boolean') {
448 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
449 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
450 die "type check ('boolean') failed - got '$value'\n";
451 } elsif ($type eq 'integer') {
452 return int($1) if $value =~ m/^(\d+)$/;
453 die "type check ('integer') failed - got '$value'\n";
454 } elsif ($type eq 'number') {
455 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
456 die "type check ('number') failed - got '$value'\n";
457 } elsif ($type eq 'string') {
458 if (my $fmt = $confdesc->{$key}->{format
}) {
459 PVE
::JSONSchema
::check_format
($fmt, $value);
468 sub parse_pct_config
{
469 my ($filename, $raw) = @_;
471 return undef if !defined($raw);
474 digest
=> Digest
::SHA
::sha1_hex
($raw),
478 $filename =~ m
|/lxc/(\d
+).conf
$|
479 || die "got strange filename '$filename'";
487 my @lines = split(/\n/, $raw);
488 foreach my $line (@lines) {
489 next if $line =~ m/^\s*$/;
491 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
493 $conf->{description
} = $descr if $descr;
495 $conf = $res->{snapshots
}->{$section} = {};
499 if ($line =~ m/^\#(.*)\s*$/) {
500 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
504 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
507 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
508 push @{$conf->{lxc
}}, [$key, $value];
510 warn "vm $vmid - unable to parse config: $line\n";
512 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
513 $descr .= PVE
::Tools
::decode_text
($2);
514 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
515 $conf->{snapstate
} = $1;
516 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
519 eval { $value = check_type
($key, $value); };
520 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
521 $conf->{$key} = $value;
523 warn "vm $vmid - unable to parse config: $line\n";
527 $conf->{description
} = $descr if $descr;
529 delete $res->{snapstate
}; # just to be sure
535 my $vmlist = PVE
::Cluster
::get_vmlist
();
537 return $res if !$vmlist || !$vmlist->{ids
};
538 my $ids = $vmlist->{ids
};
540 foreach my $vmid (keys %$ids) {
541 next if !$vmid; # skip CT0
542 my $d = $ids->{$vmid};
543 next if !$d->{node
} || $d->{node
} ne $nodename;
544 next if !$d->{type
} || $d->{type
} ne 'lxc';
545 $res->{$vmid}->{type
} = 'lxc';
550 sub cfs_config_path
{
551 my ($vmid, $node) = @_;
553 $node = $nodename if !$node;
554 return "nodes/$node/lxc/$vmid.conf";
558 my ($vmid, $node) = @_;
560 my $cfspath = cfs_config_path
($vmid, $node);
561 return "/etc/pve/$cfspath";
565 my ($vmid, $node) = @_;
567 $node = $nodename if !$node;
568 my $cfspath = cfs_config_path
($vmid, $node);
570 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
571 die "container $vmid does not exist\n" if !defined($conf);
577 my ($vmid, $conf) = @_;
579 my $dir = "/etc/pve/nodes/$nodename/lxc";
582 write_config
($vmid, $conf);
588 unlink config_file
($vmid, $nodename);
592 my ($vmid, $conf) = @_;
594 my $cfspath = cfs_config_path
($vmid);
596 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
599 # flock: we use one file handle per process, so lock file
600 # can be called multiple times and will succeed for the same process.
602 my $lock_handles = {};
603 my $lockdir = "/run/lock/lxc";
608 return "$lockdir/pve-config-${vmid}.lock";
612 my ($vmid, $timeout) = @_;
614 $timeout = 10 if !$timeout;
617 my $filename = lock_filename
($vmid);
619 mkdir $lockdir if !-d
$lockdir;
621 my $lock_func = sub {
622 if (!$lock_handles->{$$}->{$filename}) {
623 my $fh = new IO
::File
(">>$filename") ||
624 die "can't open file - $!\n";
625 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
628 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
629 print STDERR
"trying to aquire lock...";
632 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
633 # try again on EINTR (see bug #273)
634 if ($success || ($! != EINTR
)) {
639 print STDERR
" failed\n";
640 die "can't aquire lock - $!\n";
643 print STDERR
" OK\n";
646 $lock_handles->{$$}->{$filename}->{refcount
}++;
649 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
652 die "can't lock file '$filename' - $err";
659 my $filename = lock_filename
($vmid);
661 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
662 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
663 if ($refcount <= 0) {
664 $lock_handles->{$$}->{$filename} = undef;
671 my ($vmid, $timeout, $code, @param) = @_;
675 lock_aquire
($vmid, $timeout);
676 eval { $res = &$code(@param) };
688 return defined($confdesc->{$name});
691 # add JSON properties for create and set function
692 sub json_config_properties
{
695 foreach my $opt (keys %$confdesc) {
696 next if $opt eq 'parent' || $opt eq 'snaptime';
697 next if $prop->{$opt};
698 $prop->{$opt} = $confdesc->{$opt};
704 sub json_config_properties_no_rootfs
{
707 foreach my $opt (keys %$confdesc) {
708 next if $prop->{$opt};
709 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
710 $prop->{$opt} = $confdesc->{$opt};
716 # container status helpers
718 sub list_active_containers
{
720 my $filename = "/proc/net/unix";
722 # similar test is used by lcxcontainers.c: list_active_containers
725 my $fh = IO
::File-
>new ($filename, "r");
728 while (defined(my $line = <$fh>)) {
729 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
731 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
742 # warning: this is slow
746 my $active_hash = list_active_containers
();
748 return 1 if defined($active_hash->{$vmid});
753 sub get_container_disk_usage
{
754 my ($vmid, $pid) = @_;
756 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
759 my $last_proc_vmid_stat;
761 my $parse_cpuacct_stat = sub {
764 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
768 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
781 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
783 my $active_hash = list_active_containers
();
785 my $cpucount = $cpuinfo->{cpus
} || 1;
787 my $cdtime = gettimeofday
;
789 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
791 foreach my $vmid (keys %$list) {
792 my $d = $list->{$vmid};
794 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
795 warn $@ if $@; # ignore errors (consider them stopped)
797 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
799 my $cfspath = cfs_config_path
($vmid);
800 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
802 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
803 $d->{name
} =~ s/[\s]//g;
805 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
808 my $res = get_container_disk_usage
($vmid, $d->{pid
});
809 $d->{disk
} = $res->{used
};
810 $d->{maxdisk
} = $res->{total
};
813 # use 4GB by default ??
814 if (my $rootfs = $conf->{rootfs
}) {
815 my $rootinfo = parse_ct_rootfs
($rootfs);
816 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
818 $d->{maxdisk
} = 4*1024*1024*1024;
824 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
825 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
836 $d->{template
} = is_template
($conf);
839 foreach my $vmid (keys %$list) {
840 my $d = $list->{$vmid};
843 next if !$pid; # skip stopped CTs
845 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
846 $d->{uptime
} = time - $ctime; # the method lxcfs uses
848 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
849 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
851 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
852 my @bytes = split(/\n/, $blkio_bytes);
853 foreach my $byte (@bytes) {
854 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
855 $d->{diskread
} = $2 if $key eq 'Read';
856 $d->{diskwrite
} = $2 if $key eq 'Write';
860 my $pstat = &$parse_cpuacct_stat($vmid);
862 my $used = $pstat->{utime} + $pstat->{stime
};
864 my $old = $last_proc_vmid_stat->{$vmid};
866 $last_proc_vmid_stat->{$vmid} = {
874 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
877 my $dutime = $used - $old->{used
};
879 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
880 $last_proc_vmid_stat->{$vmid} = {
886 $d->{cpu
} = $old->{cpu
};
890 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
892 foreach my $dev (keys %$netdev) {
893 next if $dev !~ m/^veth([1-9]\d*)i/;
895 my $d = $list->{$vmid};
899 $d->{netout
} += $netdev->{$dev}->{receive
};
900 $d->{netin
} += $netdev->{$dev}->{transmit
};
907 sub classify_mountpoint
{
910 return 'device' if $vol =~ m!^/dev/!;
916 my $parse_ct_mountpoint_full = sub {
917 my ($desc, $data, $noerr) = @_;
922 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
924 return undef if $noerr;
928 if (defined(my $size = $res->{size
})) {
929 $size = PVE
::JSONSchema
::parse_size
($size);
930 if (!defined($size)) {
931 return undef if $noerr;
932 die "invalid size: $size\n";
934 $res->{size
} = $size;
937 $res->{type
} = classify_mountpoint
($res->{volume
});
942 sub parse_ct_rootfs
{
943 my ($data, $noerr) = @_;
945 my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
947 $res->{mp
} = '/' if defined($res);
952 sub parse_ct_mountpoint
{
953 my ($data, $noerr) = @_;
955 return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
958 sub print_ct_mountpoint
{
959 my ($info, $nomp) = @_;
960 my $skip = [ 'type' ];
961 push @$skip, 'mp' if $nomp;
962 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
965 sub print_lxc_network
{
967 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
970 sub parse_lxc_network
{
975 return $res if !$data;
977 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
979 $res->{type
} = 'veth';
980 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
985 sub read_cgroup_value
{
986 my ($group, $vmid, $name, $full) = @_;
988 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
990 return PVE
::Tools
::file_get_contents
($path) if $full;
992 return PVE
::Tools
::file_read_firstline
($path);
995 sub write_cgroup_value
{
996 my ($group, $vmid, $name, $value) = @_;
998 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
999 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
1003 sub find_lxc_console_pids
{
1007 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
1010 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
1011 return if !$cmdline;
1013 my @args = split(/\0/, $cmdline);
1015 # search for lxc-console -n <vmid>
1016 return if scalar(@args) != 3;
1017 return if $args[1] ne '-n';
1018 return if $args[2] !~ m/^\d+$/;
1019 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
1021 my $vmid = $args[2];
1023 push @{$res->{$vmid}}, $pid;
1035 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1037 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1039 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1044 # Note: we cannot use Net:IP, because that only allows strict
1046 sub parse_ipv4_cidr
{
1047 my ($cidr, $noerr) = @_;
1049 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1050 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1053 return undef if $noerr;
1055 die "unable to parse ipv4 address/mask\n";
1061 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1064 sub check_protection
{
1065 my ($vm_conf, $err_msg) = @_;
1067 if ($vm_conf->{protection
}) {
1068 die "$err_msg - protection mode enabled\n";
1072 sub update_lxc_config
{
1073 my ($storage_cfg, $vmid, $conf) = @_;
1075 my $dir = "/var/lib/lxc/$vmid";
1077 if ($conf->{template
}) {
1079 unlink "$dir/config";
1086 die "missing 'arch' - internal error" if !$conf->{arch
};
1087 $raw .= "lxc.arch = $conf->{arch}\n";
1089 my $unprivileged = $conf->{unprivileged
};
1090 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1092 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1093 if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux)$/x) {
1094 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1095 if ($unprivileged || $custom_idmap) {
1096 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
1099 die "implement me (ostype $ostype)";
1102 $raw .= "lxc.monitor.unshare = 1\n";
1104 # Should we read them from /etc/subuid?
1105 if ($unprivileged && !$custom_idmap) {
1106 $raw .= "lxc.id_map = u 0 100000 65536\n";
1107 $raw .= "lxc.id_map = g 0 100000 65536\n";
1110 if (!has_dev_console
($conf)) {
1111 $raw .= "lxc.console = none\n";
1112 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1115 my $ttycount = get_tty_count
($conf);
1116 $raw .= "lxc.tty = $ttycount\n";
1118 # some init scripts expect a linux terminal (turnkey).
1119 $raw .= "lxc.environment = TERM=linux\n";
1121 my $utsname = $conf->{hostname
} || "CT$vmid";
1122 $raw .= "lxc.utsname = $utsname\n";
1124 my $memory = $conf->{memory
} || 512;
1125 my $swap = $conf->{swap
} // 0;
1127 my $lxcmem = int($memory*1024*1024);
1128 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1130 my $lxcswap = int(($memory + $swap)*1024*1024);
1131 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1133 if (my $cpulimit = $conf->{cpulimit
}) {
1134 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1135 my $value = int(100000*$cpulimit);
1136 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1139 my $shares = $conf->{cpuunits
} || 1024;
1140 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1142 my $mountpoint = parse_ct_rootfs
($conf->{rootfs
});
1144 $raw .= "lxc.rootfs = $dir/rootfs\n";
1147 foreach my $k (keys %$conf) {
1148 next if $k !~ m/^net(\d+)$/;
1150 my $d = parse_lxc_network
($conf->{$k});
1152 $raw .= "lxc.network.type = veth\n";
1153 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1154 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1155 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1156 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1159 if (my $lxcconf = $conf->{lxc
}) {
1160 foreach my $entry (@$lxcconf) {
1161 my ($k, $v) = @$entry;
1162 $netcount++ if $k eq 'lxc.network.type';
1163 $raw .= "$k = $v\n";
1167 $raw .= "lxc.network.type = empty\n" if !$netcount;
1169 File
::Path
::mkpath
("$dir/rootfs");
1171 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1174 # verify and cleanup nameserver list (replace \0 with ' ')
1175 sub verify_nameserver_list
{
1176 my ($nameserver_list) = @_;
1179 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1180 PVE
::JSONSchema
::pve_verify_ip
($server);
1181 push @list, $server;
1184 return join(' ', @list);
1187 sub verify_searchdomain_list
{
1188 my ($searchdomain_list) = @_;
1191 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1192 # todo: should we add checks for valid dns domains?
1193 push @list, $server;
1196 return join(' ', @list);
1199 sub add_unused_volume
{
1200 my ($config, $volid) = @_;
1203 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1204 my $test = "unused$ind";
1205 if (my $vid = $config->{$test}) {
1206 return if $vid eq $volid; # do not add duplicates
1212 die "Too many unused volumes - please delete them first.\n" if !$key;
1214 $config->{$key} = $volid;
1219 sub update_pct_config
{
1220 my ($vmid, $conf, $running, $param, $delete) = @_;
1225 my @deleted_volumes;
1229 my $pid = find_lxc_pid
($vmid);
1230 $rootdir = "/proc/$pid/root";
1233 my $hotplug_error = sub {
1235 push @nohotplug, @_;
1242 if (defined($delete)) {
1243 foreach my $opt (@$delete) {
1244 if (!exists($conf->{$opt})) {
1245 warn "no such option: $opt\n";
1249 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1250 die "unable to delete required option '$opt'\n";
1251 } elsif ($opt eq 'swap') {
1252 delete $conf->{$opt};
1253 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1254 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1255 delete $conf->{$opt};
1256 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1257 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1258 next if $hotplug_error->($opt);
1259 delete $conf->{$opt};
1260 } elsif ($opt =~ m/^net(\d)$/) {
1261 delete $conf->{$opt};
1264 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1265 } elsif ($opt eq 'protection') {
1266 delete $conf->{$opt};
1267 } elsif ($opt =~ m/^unused(\d+)$/) {
1268 next if $hotplug_error->($opt);
1269 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1270 push @deleted_volumes, $conf->{$opt};
1271 delete $conf->{$opt};
1272 } elsif ($opt =~ m/^mp(\d+)$/) {
1273 next if $hotplug_error->($opt);
1274 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1275 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1276 if ($mountpoint->{type
} eq 'volume') {
1277 add_unused_volume
($conf, $mountpoint->{volume
})
1279 delete $conf->{$opt};
1280 } elsif ($opt eq 'unprivileged') {
1281 die "unable to delete read-only option: '$opt'\n";
1283 die "implement me (delete: $opt)"
1285 write_config
($vmid, $conf) if $running;
1289 # There's no separate swap size to configure, there's memory and "total"
1290 # memory (iow. memory+swap). This means we have to change them together.
1291 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1292 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1293 if (defined($wanted_memory) || defined($wanted_swap)) {
1295 $wanted_memory //= ($conf->{memory
} || 512);
1296 $wanted_swap //= ($conf->{swap
} || 0);
1298 my $total = $wanted_memory + $wanted_swap;
1300 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1301 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1303 $conf->{memory
} = $wanted_memory;
1304 $conf->{swap
} = $wanted_swap;
1306 write_config
($vmid, $conf) if $running;
1309 foreach my $opt (keys %$param) {
1310 my $value = $param->{$opt};
1311 if ($opt eq 'hostname') {
1312 $conf->{$opt} = $value;
1313 } elsif ($opt eq 'onboot') {
1314 $conf->{$opt} = $value ?
1 : 0;
1315 } elsif ($opt eq 'startup') {
1316 $conf->{$opt} = $value;
1317 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1318 next if $hotplug_error->($opt);
1319 $conf->{$opt} = $value;
1320 } elsif ($opt eq 'nameserver') {
1321 next if $hotplug_error->($opt);
1322 my $list = verify_nameserver_list
($value);
1323 $conf->{$opt} = $list;
1324 } elsif ($opt eq 'searchdomain') {
1325 next if $hotplug_error->($opt);
1326 my $list = verify_searchdomain_list
($value);
1327 $conf->{$opt} = $list;
1328 } elsif ($opt eq 'cpulimit') {
1329 next if $hotplug_error->($opt); # FIXME: hotplug
1330 $conf->{$opt} = $value;
1331 } elsif ($opt eq 'cpuunits') {
1332 $conf->{$opt} = $value;
1333 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1334 } elsif ($opt eq 'description') {
1335 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1336 } elsif ($opt =~ m/^net(\d+)$/) {
1338 my $net = parse_lxc_network
($value);
1340 $conf->{$opt} = print_lxc_network
($net);
1342 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1344 } elsif ($opt eq 'protection') {
1345 $conf->{$opt} = $value ?
1 : 0;
1346 } elsif ($opt =~ m/^mp(\d+)$/) {
1347 next if $hotplug_error->($opt);
1348 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1349 $conf->{$opt} = $value;
1351 } elsif ($opt eq 'rootfs') {
1352 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1353 die "implement me: $opt";
1354 } elsif ($opt eq 'unprivileged') {
1355 die "unable to modify read-only option: '$opt'\n";
1357 die "implement me: $opt";
1359 write_config
($vmid, $conf) if $running;
1362 if (@deleted_volumes) {
1363 my $storage_cfg = PVE
::Storage
::config
();
1364 foreach my $volume (@deleted_volumes) {
1365 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1370 my $storage_cfg = PVE
::Storage
::config
();
1371 create_disks
($storage_cfg, $vmid, $conf, $conf);
1374 # This should be the last thing we do here
1375 if ($running && scalar(@nohotplug)) {
1376 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1380 sub has_dev_console
{
1383 return !(defined($conf->{console
}) && !$conf->{console
});
1389 return $conf->{tty
} // $confdesc->{tty
}->{default};
1395 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1398 sub get_console_command
{
1399 my ($vmid, $conf) = @_;
1401 my $cmode = get_cmode
($conf);
1403 if ($cmode eq 'console') {
1404 return ['lxc-console', '-n', $vmid, '-t', 0];
1405 } elsif ($cmode eq 'tty') {
1406 return ['lxc-console', '-n', $vmid];
1407 } elsif ($cmode eq 'shell') {
1408 return ['lxc-attach', '--clear-env', '-n', $vmid];
1410 die "internal error";
1414 sub get_primary_ips
{
1417 # return data from net0
1419 return undef if !defined($conf->{net0
});
1420 my $net = parse_lxc_network
($conf->{net0
});
1422 my $ipv4 = $net->{ip
};
1424 if ($ipv4 =~ /^(dhcp|manual)$/) {
1430 my $ipv6 = $net->{ip6
};
1432 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1439 return ($ipv4, $ipv6);
1442 sub delete_mountpoint_volume
{
1443 my ($storage_cfg, $vmid, $volume) = @_;
1445 return if classify_mountpoint
($volume) ne 'volume';
1447 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1448 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1451 sub destroy_lxc_container
{
1452 my ($storage_cfg, $vmid, $conf) = @_;
1454 foreach_mountpoint
($conf, sub {
1455 my ($ms, $mountpoint) = @_;
1456 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1459 rmdir "/var/lib/lxc/$vmid/rootfs";
1460 unlink "/var/lib/lxc/$vmid/config";
1461 rmdir "/var/lib/lxc/$vmid";
1462 destroy_config
($vmid);
1464 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1465 #PVE::Tools::run_command($cmd);
1468 sub vm_stop_cleanup
{
1469 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1474 my $vollist = get_vm_volumes
($conf);
1475 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1478 warn $@ if $@; # avoid errors - just warn
1481 my $safe_num_ne = sub {
1484 return 0 if !defined($a) && !defined($b);
1485 return 1 if !defined($a);
1486 return 1 if !defined($b);
1491 my $safe_string_ne = sub {
1494 return 0 if !defined($a) && !defined($b);
1495 return 1 if !defined($a);
1496 return 1 if !defined($b);
1502 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1504 if ($newnet->{type
} ne 'veth') {
1505 # for when there are physical interfaces
1506 die "cannot update interface of type $newnet->{type}";
1509 my $veth = "veth${vmid}i${netid}";
1510 my $eth = $newnet->{name
};
1512 if (my $oldnetcfg = $conf->{$opt}) {
1513 my $oldnet = parse_lxc_network
($oldnetcfg);
1515 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1516 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1518 PVE
::Network
::veth_delete
($veth);
1519 delete $conf->{$opt};
1520 write_config
($vmid, $conf);
1522 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1524 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1525 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1526 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1528 if ($oldnet->{bridge
}) {
1529 PVE
::Network
::tap_unplug
($veth);
1530 foreach (qw(bridge tag firewall)) {
1531 delete $oldnet->{$_};
1533 $conf->{$opt} = print_lxc_network
($oldnet);
1534 write_config
($vmid, $conf);
1537 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1538 foreach (qw(bridge tag firewall)) {
1539 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1541 $conf->{$opt} = print_lxc_network
($oldnet);
1542 write_config
($vmid, $conf);
1545 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1548 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1552 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1554 my $veth = "veth${vmid}i${netid}";
1555 my $vethpeer = $veth . "p";
1556 my $eth = $newnet->{name
};
1558 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1559 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1561 # attach peer in container
1562 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1563 PVE
::Tools
::run_command
($cmd);
1565 # link up peer in container
1566 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1567 PVE
::Tools
::run_command
($cmd);
1569 my $done = { type
=> 'veth' };
1570 foreach (qw(bridge tag firewall hwaddr name)) {
1571 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1573 $conf->{$opt} = print_lxc_network
($done);
1575 write_config
($vmid, $conf);
1578 sub update_ipconfig
{
1579 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1581 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1583 my $optdata = parse_lxc_network
($conf->{$opt});
1587 my $cmdargs = shift;
1588 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1590 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1592 my $change_ip_config = sub {
1593 my ($ipversion) = @_;
1595 my $family_opt = "-$ipversion";
1596 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1597 my $gw= "gw$suffix";
1598 my $ip= "ip$suffix";
1600 my $newip = $newnet->{$ip};
1601 my $newgw = $newnet->{$gw};
1602 my $oldip = $optdata->{$ip};
1604 my $change_ip = &$safe_string_ne($oldip, $newip);
1605 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1607 return if !$change_ip && !$change_gw;
1609 # step 1: add new IP, if this fails we cancel
1610 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1611 if ($change_ip && $is_real_ip) {
1612 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1619 # step 2: replace gateway
1620 # If this fails we delete the added IP and cancel.
1621 # If it succeeds we save the config and delete the old IP, ignoring
1622 # errors. The config is then saved.
1623 # Note: 'ip route replace' can add
1627 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1628 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1630 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1634 # the route was not replaced, the old IP is still available
1635 # rollback (delete new IP) and cancel
1637 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1638 warn $@ if $@; # no need to die here
1643 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1644 # if the route was not deleted, the guest might have deleted it manually
1650 # from this point on we save the configuration
1651 # step 3: delete old IP ignoring errors
1652 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1653 # We need to enable promote_secondaries, otherwise our newly added
1654 # address will be removed along with the old one.
1657 if ($ipversion == 4) {
1658 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1659 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1660 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1662 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1664 warn $@ if $@; # no need to die here
1666 if ($ipversion == 4) {
1667 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1671 foreach my $property ($ip, $gw) {
1672 if ($newnet->{$property}) {
1673 $optdata->{$property} = $newnet->{$property};
1675 delete $optdata->{$property};
1678 $conf->{$opt} = print_lxc_network
($optdata);
1679 write_config
($vmid, $conf);
1680 $lxc_setup->setup_network($conf);
1683 &$change_ip_config(4);
1684 &$change_ip_config(6);
1688 # Internal snapshots
1690 # NOTE: Snapshot create/delete involves several non-atomic
1691 # actions, and can take a long time.
1692 # So we try to avoid locking the file and use the 'lock' variable
1693 # inside the config file instead.
1695 my $snapshot_copy_config = sub {
1696 my ($source, $dest) = @_;
1698 foreach my $k (keys %$source) {
1699 next if $k eq 'snapshots';
1700 next if $k eq 'snapstate';
1701 next if $k eq 'snaptime';
1702 next if $k eq 'vmstate';
1703 next if $k eq 'lock';
1704 next if $k eq 'digest';
1705 next if $k eq 'description';
1707 $dest->{$k} = $source->{$k};
1711 my $snapshot_prepare = sub {
1712 my ($vmid, $snapname, $comment) = @_;
1716 my $updatefn = sub {
1718 my $conf = load_config
($vmid);
1720 die "you can't take a snapshot if it's a template\n"
1721 if is_template
($conf);
1725 $conf->{lock} = 'snapshot';
1727 die "snapshot name '$snapname' already used\n"
1728 if defined($conf->{snapshots
}->{$snapname});
1730 my $storecfg = PVE
::Storage
::config
();
1731 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1733 $snap = $conf->{snapshots
}->{$snapname} = {};
1735 &$snapshot_copy_config($conf, $snap);
1737 $snap->{'snapstate'} = "prepare";
1738 $snap->{'snaptime'} = time();
1739 $snap->{'description'} = $comment if $comment;
1740 $conf->{snapshots
}->{$snapname} = $snap;
1742 write_config
($vmid, $conf);
1745 lock_container
($vmid, 10, $updatefn);
1750 my $snapshot_commit = sub {
1751 my ($vmid, $snapname) = @_;
1753 my $updatefn = sub {
1755 my $conf = load_config
($vmid);
1757 die "missing snapshot lock\n"
1758 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1760 die "snapshot '$snapname' does not exist\n"
1761 if !defined($conf->{snapshots
}->{$snapname});
1763 die "wrong snapshot state\n"
1764 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1765 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1767 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1768 delete $conf->{lock};
1769 $conf->{parent
} = $snapname;
1771 write_config
($vmid, $conf);
1774 lock_container
($vmid, 10 ,$updatefn);
1778 my ($feature, $conf, $storecfg, $snapname) = @_;
1782 foreach_mountpoint
($conf, sub {
1783 my ($ms, $mountpoint) = @_;
1785 return if $err; # skip further test
1787 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1789 # TODO: implement support for mountpoints
1790 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1794 return $err ?
0 : 1;
1797 sub snapshot_create
{
1798 my ($vmid, $snapname, $comment) = @_;
1800 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1802 my $conf = load_config
($vmid);
1804 my $running = check_running
($vmid);
1810 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1812 PVE
::Tools
::run_command
(['/bin/sync']);
1815 my $storecfg = PVE
::Storage
::config
();
1816 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1817 my $volid = $rootinfo->{volume
};
1819 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1820 &$snapshot_commit($vmid, $snapname);
1825 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
1830 snapshot_delete
($vmid, $snapname, 1);
1835 sub snapshot_delete
{
1836 my ($vmid, $snapname, $force) = @_;
1842 my $updatefn = sub {
1844 $conf = load_config
($vmid);
1846 die "you can't delete a snapshot if vm is a template\n"
1847 if is_template
($conf);
1849 $snap = $conf->{snapshots
}->{$snapname};
1853 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1855 $snap->{snapstate
} = 'delete';
1857 write_config
($vmid, $conf);
1860 lock_container
($vmid, 10, $updatefn);
1862 my $storecfg = PVE
::Storage
::config
();
1864 my $unlink_parent = sub {
1866 my ($confref, $new_parent) = @_;
1868 if ($confref->{parent
} && $confref->{parent
} eq $snapname) {
1870 $confref->{parent
} = $new_parent;
1872 delete $confref->{parent
};
1877 my $del_snap = sub {
1881 my $parent = $conf->{snapshots
}->{$snapname}->{parent
};
1882 foreach my $snapkey (keys %{$conf->{snapshots
}}) {
1883 &$unlink_parent($conf->{snapshots
}->{$snapkey}, $parent);
1886 &$unlink_parent($conf, $parent);
1888 delete $conf->{snapshots
}->{$snapname};
1890 write_config
($vmid, $conf);
1893 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1894 my $rootinfo = parse_ct_rootfs
($rootfs);
1895 my $volid = $rootinfo->{volume
};
1898 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1902 if(!$err || ($err && $force)) {
1903 lock_container
($vmid, 10, $del_snap);
1905 die "Can't delete snapshot: $vmid $snapname $err\n";
1910 sub snapshot_rollback
{
1911 my ($vmid, $snapname) = @_;
1913 my $storecfg = PVE
::Storage
::config
();
1915 my $conf = load_config
($vmid);
1917 die "you can't rollback if vm is a template\n" if is_template
($conf);
1919 my $snap = $conf->{snapshots
}->{$snapname};
1921 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1923 my $rootfs = $snap->{rootfs
};
1924 my $rootinfo = parse_ct_rootfs
($rootfs);
1925 my $volid = $rootinfo->{volume
};
1927 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1929 my $updatefn = sub {
1931 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1932 if $snap->{snapstate
};
1936 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1938 die "unable to rollback vm $vmid: vm is running\n"
1939 if check_running
($vmid);
1941 $conf->{lock} = 'rollback';
1945 # copy snapshot config to current config
1947 my $tmp_conf = $conf;
1948 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1949 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1950 delete $conf->{snaptime
};
1951 delete $conf->{snapname
};
1952 $conf->{parent
} = $snapname;
1954 write_config
($vmid, $conf);
1957 my $unlockfn = sub {
1958 delete $conf->{lock};
1959 write_config
($vmid, $conf);
1962 lock_container
($vmid, 10, $updatefn);
1964 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1966 lock_container
($vmid, 5, $unlockfn);
1969 sub template_create
{
1970 my ($vmid, $conf) = @_;
1972 my $storecfg = PVE
::Storage
::config
();
1974 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1975 my $volid = $rootinfo->{volume
};
1977 die "Template feature is not available for '$volid'\n"
1978 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1980 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1982 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1983 $rootinfo->{volume
} = $template_volid;
1984 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1986 write_config
($vmid, $conf);
1992 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1995 sub mountpoint_names
{
1998 my @names = ('rootfs');
2000 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
2001 push @names, "mp$i";
2004 return $reverse ?
reverse @names : @names;
2007 # The container might have *different* symlinks than the host. realpath/abs_path
2008 # use the actual filesystem to resolve links.
2009 sub sanitize_mountpoint
{
2011 $mp = '/' . $mp; # we always start with a slash
2012 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
2013 $mp =~ s
@/\./@@g; # collapse /./
2014 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
2015 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
2016 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
2020 sub foreach_mountpoint_full
{
2021 my ($conf, $reverse, $func) = @_;
2023 foreach my $key (mountpoint_names
($reverse)) {
2024 my $value = $conf->{$key};
2025 next if !defined($value);
2026 my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs
($value, 1) : parse_ct_mountpoint
($value, 1);
2027 next if !defined($mountpoint);
2029 $mountpoint->{mp
} = sanitize_mountpoint
($mountpoint->{mp
});
2031 my $path = $mountpoint->{volume
};
2032 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
2034 &$func($key, $mountpoint);
2038 sub foreach_mountpoint
{
2039 my ($conf, $func) = @_;
2041 foreach_mountpoint_full
($conf, 0, $func);
2044 sub foreach_mountpoint_reverse
{
2045 my ($conf, $func) = @_;
2047 foreach_mountpoint_full
($conf, 1, $func);
2050 sub check_ct_modify_config_perm
{
2051 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2053 return 1 if $authuser ne 'root@pam';
2055 foreach my $opt (@$key_list) {
2057 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2058 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2059 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2060 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2061 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2062 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2063 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2064 $opt eq 'searchdomain' || $opt eq 'hostname') {
2065 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2067 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2075 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2077 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2078 my $volid_list = get_vm_volumes
($conf);
2080 foreach_mountpoint_reverse
($conf, sub {
2081 my ($ms, $mountpoint) = @_;
2083 my $volid = $mountpoint->{volume
};
2084 my $mount = $mountpoint->{mp
};
2086 return if !$volid || !$mount;
2088 my $mount_path = "$rootdir/$mount";
2089 $mount_path =~ s!/+!/!g;
2091 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2094 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2107 my ($vmid, $storage_cfg, $conf) = @_;
2109 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2110 File
::Path
::make_path
($rootdir);
2112 my $volid_list = get_vm_volumes
($conf);
2113 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2116 foreach_mountpoint
($conf, sub {
2117 my ($ms, $mountpoint) = @_;
2119 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2123 warn "mounting container failed\n";
2124 umount_all
($vmid, $storage_cfg, $conf, 1);
2132 sub mountpoint_mount_path
{
2133 my ($mountpoint, $storage_cfg, $snapname) = @_;
2135 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2138 my $check_mount_path = sub {
2140 $path = File
::Spec-
>canonpath($path);
2141 my $real = Cwd
::realpath
($path);
2142 if ($real ne $path) {
2143 die "mount path modified by symlink: $path != $real";
2152 if ($line =~ m
@^(/dev/loop\d
+):@) {
2156 my $cmd = ['losetup', '--associated', $path];
2157 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2161 # use $rootdir = undef to just return the corresponding mount path
2162 sub mountpoint_mount
{
2163 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2165 my $volid = $mountpoint->{volume
};
2166 my $mount = $mountpoint->{mp
};
2167 my $type = $mountpoint->{type
};
2169 return if !$volid || !$mount;
2173 if (defined($rootdir)) {
2174 $rootdir =~ s!/+$!!;
2175 $mount_path = "$rootdir/$mount";
2176 $mount_path =~ s!/+!/!g;
2177 &$check_mount_path($mount_path);
2178 File
::Path
::mkpath
($mount_path);
2181 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2183 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2187 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2188 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2190 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2191 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2193 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2195 if ($format eq 'subvol') {
2198 if ($scfg->{type
} eq 'zfspool') {
2199 my $path_arg = $path;
2200 $path_arg =~ s!^/+!!;
2201 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2203 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2206 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2209 return wantarray ?
($path, 0) : $path;
2210 } elsif ($format eq 'raw' || $format eq 'iso') {
2211 my $use_loopdev = 0;
2213 if ($scfg->{path
}) {
2214 push @extra_opts, '-o', 'loop';
2216 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' ||
2217 $scfg->{type
} eq 'rbd' || $scfg->{type
} eq 'lvmthin') {
2220 die "unsupported storage type '$scfg->{type}'\n";
2223 if ($format eq 'iso') {
2224 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2225 } elsif ($isBase || defined($snapname)) {
2226 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2228 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2231 return wantarray ?
($path, $use_loopdev) : $path;
2233 die "unsupported image format '$format'\n";
2235 } elsif ($type eq 'device') {
2236 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2237 return wantarray ?
($volid, 0) : $volid;
2238 } elsif ($type eq 'bind') {
2239 die "directory '$volid' does not exist\n" if ! -d
$volid;
2240 &$check_mount_path($volid);
2241 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2242 return wantarray ?
($volid, 0) : $volid;
2245 die "unsupported storage";
2248 sub get_vm_volumes
{
2249 my ($conf, $excludes) = @_;
2253 foreach_mountpoint
($conf, sub {
2254 my ($ms, $mountpoint) = @_;
2256 return if $excludes && $ms eq $excludes;
2258 my $volid = $mountpoint->{volume
};
2260 return if !$volid || $mountpoint->{type
} ne 'volume';
2262 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2265 push @$vollist, $volid;
2272 my ($dev, $rootuid, $rootgid) = @_;
2274 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2275 '-E', "root_owner=$rootuid:$rootgid",
2280 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2282 if ($volid =~ m!^/dev/.+!) {
2287 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2289 die "cannot format volume '$volid' with no storage\n" if !$storage;
2291 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2293 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2295 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2296 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2298 die "cannot format volume '$volid' (format == $format)\n"
2299 if $format ne 'raw';
2301 mkfs
($path, $rootuid, $rootgid);
2305 my ($storecfg, $vollist) = @_;
2307 foreach my $volid (@$vollist) {
2308 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2314 my ($storecfg, $vmid, $settings, $conf) = @_;
2319 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2320 my $chown_vollist = [];
2322 foreach_mountpoint
($settings, sub {
2323 my ($ms, $mountpoint) = @_;
2325 my $volid = $mountpoint->{volume
};
2326 my $mp = $mountpoint->{mp
};
2328 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2330 if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
2331 my ($storeid, $size_gb) = ($1, $2);
2333 my $size_kb = int(${size_gb
}*1024) * 1024;
2335 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2336 # fixme: use better naming ct-$vmid-disk-X.raw?
2338 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2340 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2342 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2344 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2346 push @$chown_vollist, $volid;
2348 } elsif ($scfg->{type
} eq 'zfspool') {
2350 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2352 push @$chown_vollist, $volid;
2353 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'lvmthin') {
2355 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2356 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2358 } elsif ($scfg->{type
} eq 'rbd') {
2360 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2361 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2362 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2364 die "unable to create containers on storage type '$scfg->{type}'\n";
2366 push @$vollist, $volid;
2367 $mountpoint->{volume
} = $volid;
2368 $mountpoint->{size
} = $size_kb * 1024;
2369 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2371 # use specified/existing volid/dir/device
2372 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2376 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2377 foreach my $volid (@$chown_vollist) {
2378 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2379 chown($rootuid, $rootgid, $path);
2381 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2383 # free allocated images on error
2385 destroy_disks
($storecfg, $vollist);
2391 # bash completion helper
2393 sub complete_os_templates
{
2394 my ($cmdname, $pname, $cvalue) = @_;
2396 my $cfg = PVE
::Storage
::config
();
2400 if ($cvalue =~ m/^([^:]+):/) {
2404 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2405 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2408 foreach my $id (keys %$data) {
2409 foreach my $item (@{$data->{$id}}) {
2410 push @$res, $item->{volid
} if defined($item->{volid
});
2417 my $complete_ctid_full = sub {
2420 my $idlist = vmstatus
();
2422 my $active_hash = list_active_containers
();
2426 foreach my $id (keys %$idlist) {
2427 my $d = $idlist->{$id};
2428 if (defined($running)) {
2429 next if $d->{template
};
2430 next if $running && !$active_hash->{$id};
2431 next if !$running && $active_hash->{$id};
2440 return &$complete_ctid_full();
2443 sub complete_ctid_stopped
{
2444 return &$complete_ctid_full(0);
2447 sub complete_ctid_running
{
2448 return &$complete_ctid_full(1);
2458 my $lxc = $conf->{lxc
};
2459 foreach my $entry (@$lxc) {
2460 my ($key, $value) = @$entry;
2461 next if $key ne 'lxc.id_map';
2462 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2463 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2464 push @$id_map, [$type, $ct, $host, $length];
2466 $rootuid = $host if $type eq 'u';
2467 $rootgid = $host if $type eq 'g';
2470 die "failed to parse id_map: $value\n";
2474 if (!@$id_map && $conf->{unprivileged
}) {
2475 # Should we read them from /etc/subuid?
2476 $id_map = [ ['u', '0', '100000', '65536'],
2477 ['g', '0', '100000', '65536'] ];
2478 $rootuid = $rootgid = 100000;
2481 return ($id_map, $rootuid, $rootgid);
2484 sub userns_command
{
2487 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];