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 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.',
362 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
366 type
=> 'string', format
=> 'pve-volume-id',
367 description
=> "Reference to unused volumes.",
370 my $MAX_MOUNT_POINTS = 10;
371 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
372 $confdesc->{"mp$i"} = {
374 type
=> 'string', format
=> $mp_desc,
375 description
=> "Use volume as container mount point (experimental feature).",
380 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
381 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
382 $confdesc->{"unused$i"} = $unuseddesc;
385 sub write_pct_config
{
386 my ($filename, $conf) = @_;
388 delete $conf->{snapstate
}; # just to be sure
390 my $generate_raw_config = sub {
395 # add description as comment to top of file
396 my $descr = $conf->{description
} || '';
397 foreach my $cl (split(/\n/, $descr)) {
398 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
401 foreach my $key (sort keys %$conf) {
402 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
403 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
404 my $value = $conf->{$key};
405 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
406 $raw .= "$key: $value\n";
409 if (my $lxcconf = $conf->{lxc
}) {
410 foreach my $entry (@$lxcconf) {
411 my ($k, $v) = @$entry;
419 my $raw = &$generate_raw_config($conf);
421 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
422 $raw .= "\n[$snapname]\n";
423 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
430 my ($key, $value) = @_;
432 die "unknown setting '$key'\n" if !$confdesc->{$key};
434 my $type = $confdesc->{$key}->{type
};
436 if (!defined($value)) {
437 die "got undefined value\n";
440 if ($value =~ m/[\n\r]/) {
441 die "property contains a line feed\n";
444 if ($type eq 'boolean') {
445 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
446 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
447 die "type check ('boolean') failed - got '$value'\n";
448 } elsif ($type eq 'integer') {
449 return int($1) if $value =~ m/^(\d+)$/;
450 die "type check ('integer') failed - got '$value'\n";
451 } elsif ($type eq 'number') {
452 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
453 die "type check ('number') failed - got '$value'\n";
454 } elsif ($type eq 'string') {
455 if (my $fmt = $confdesc->{$key}->{format
}) {
456 PVE
::JSONSchema
::check_format
($fmt, $value);
465 sub parse_pct_config
{
466 my ($filename, $raw) = @_;
468 return undef if !defined($raw);
471 digest
=> Digest
::SHA
::sha1_hex
($raw),
475 $filename =~ m
|/lxc/(\d
+).conf
$|
476 || die "got strange filename '$filename'";
484 my @lines = split(/\n/, $raw);
485 foreach my $line (@lines) {
486 next if $line =~ m/^\s*$/;
488 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
490 $conf->{description
} = $descr if $descr;
492 $conf = $res->{snapshots
}->{$section} = {};
496 if ($line =~ m/^\#(.*)\s*$/) {
497 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
501 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
504 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
505 push @{$conf->{lxc
}}, [$key, $value];
507 warn "vm $vmid - unable to parse config: $line\n";
509 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
510 $descr .= PVE
::Tools
::decode_text
($2);
511 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
512 $conf->{snapstate
} = $1;
513 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
516 eval { $value = check_type
($key, $value); };
517 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
518 $conf->{$key} = $value;
520 warn "vm $vmid - unable to parse config: $line\n";
524 $conf->{description
} = $descr if $descr;
526 delete $res->{snapstate
}; # just to be sure
532 my $vmlist = PVE
::Cluster
::get_vmlist
();
534 return $res if !$vmlist || !$vmlist->{ids
};
535 my $ids = $vmlist->{ids
};
537 foreach my $vmid (keys %$ids) {
538 next if !$vmid; # skip CT0
539 my $d = $ids->{$vmid};
540 next if !$d->{node
} || $d->{node
} ne $nodename;
541 next if !$d->{type
} || $d->{type
} ne 'lxc';
542 $res->{$vmid}->{type
} = 'lxc';
547 sub cfs_config_path
{
548 my ($vmid, $node) = @_;
550 $node = $nodename if !$node;
551 return "nodes/$node/lxc/$vmid.conf";
555 my ($vmid, $node) = @_;
557 my $cfspath = cfs_config_path
($vmid, $node);
558 return "/etc/pve/$cfspath";
562 my ($vmid, $node) = @_;
564 $node = $nodename if !$node;
565 my $cfspath = cfs_config_path
($vmid, $node);
567 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
568 die "container $vmid does not exists\n" if !defined($conf);
574 my ($vmid, $conf) = @_;
576 my $dir = "/etc/pve/nodes/$nodename/lxc";
579 write_config
($vmid, $conf);
585 unlink config_file
($vmid, $nodename);
589 my ($vmid, $conf) = @_;
591 my $cfspath = cfs_config_path
($vmid);
593 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
596 # flock: we use one file handle per process, so lock file
597 # can be called multiple times and succeeds for the same process.
599 my $lock_handles = {};
600 my $lockdir = "/run/lock/lxc";
605 return "$lockdir/pve-config-${vmid}.lock";
609 my ($vmid, $timeout) = @_;
611 $timeout = 10 if !$timeout;
614 my $filename = lock_filename
($vmid);
616 mkdir $lockdir if !-d
$lockdir;
618 my $lock_func = sub {
619 if (!$lock_handles->{$$}->{$filename}) {
620 my $fh = new IO
::File
(">>$filename") ||
621 die "can't open file - $!\n";
622 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
625 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
626 print STDERR
"trying to aquire lock...";
629 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
630 # try again on EINTR (see bug #273)
631 if ($success || ($! != EINTR
)) {
636 print STDERR
" failed\n";
637 die "can't aquire lock - $!\n";
640 print STDERR
" OK\n";
643 $lock_handles->{$$}->{$filename}->{refcount
}++;
646 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
649 die "can't lock file '$filename' - $err";
656 my $filename = lock_filename
($vmid);
658 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
659 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
660 if ($refcount <= 0) {
661 $lock_handles->{$$}->{$filename} = undef;
668 my ($vmid, $timeout, $code, @param) = @_;
672 lock_aquire
($vmid, $timeout);
673 eval { $res = &$code(@param) };
685 return defined($confdesc->{$name});
688 # add JSON properties for create and set function
689 sub json_config_properties
{
692 foreach my $opt (keys %$confdesc) {
693 next if $opt eq 'parent' || $opt eq 'snaptime';
694 next if $prop->{$opt};
695 $prop->{$opt} = $confdesc->{$opt};
701 sub json_config_properties_no_rootfs
{
704 foreach my $opt (keys %$confdesc) {
705 next if $prop->{$opt};
706 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
707 $prop->{$opt} = $confdesc->{$opt};
713 # container status helpers
715 sub list_active_containers
{
717 my $filename = "/proc/net/unix";
719 # similar test is used by lcxcontainers.c: list_active_containers
722 my $fh = IO
::File-
>new ($filename, "r");
725 while (defined(my $line = <$fh>)) {
726 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
728 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
739 # warning: this is slow
743 my $active_hash = list_active_containers
();
745 return 1 if defined($active_hash->{$vmid});
750 sub get_container_disk_usage
{
751 my ($vmid, $pid) = @_;
753 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
756 my $last_proc_vmid_stat;
758 my $parse_cpuacct_stat = sub {
761 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
765 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
778 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
780 my $active_hash = list_active_containers
();
782 my $cpucount = $cpuinfo->{cpus
} || 1;
784 my $cdtime = gettimeofday
;
786 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
788 foreach my $vmid (keys %$list) {
789 my $d = $list->{$vmid};
791 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
792 warn $@ if $@; # ignore errors (consider them stopped)
794 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
796 my $cfspath = cfs_config_path
($vmid);
797 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
799 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
800 $d->{name
} =~ s/[\s]//g;
802 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
805 my $res = get_container_disk_usage
($vmid, $d->{pid
});
806 $d->{disk
} = $res->{used
};
807 $d->{maxdisk
} = $res->{total
};
810 # use 4GB by default ??
811 if (my $rootfs = $conf->{rootfs
}) {
812 my $rootinfo = parse_ct_rootfs
($rootfs);
813 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
815 $d->{maxdisk
} = 4*1024*1024*1024;
821 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
822 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
833 $d->{template
} = is_template
($conf);
836 foreach my $vmid (keys %$list) {
837 my $d = $list->{$vmid};
840 next if !$pid; # skip stopped CTs
842 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
843 $d->{uptime
} = time - $ctime; # the method lxcfs uses
845 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
846 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
848 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
849 my @bytes = split(/\n/, $blkio_bytes);
850 foreach my $byte (@bytes) {
851 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
852 $d->{diskread
} = $2 if $key eq 'Read';
853 $d->{diskwrite
} = $2 if $key eq 'Write';
857 my $pstat = &$parse_cpuacct_stat($vmid);
859 my $used = $pstat->{utime} + $pstat->{stime
};
861 my $old = $last_proc_vmid_stat->{$vmid};
863 $last_proc_vmid_stat->{$vmid} = {
871 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
874 my $dutime = $used - $old->{used
};
876 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
877 $last_proc_vmid_stat->{$vmid} = {
883 $d->{cpu
} = $old->{cpu
};
887 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
889 foreach my $dev (keys %$netdev) {
890 next if $dev !~ m/^veth([1-9]\d*)i/;
892 my $d = $list->{$vmid};
896 $d->{netout
} += $netdev->{$dev}->{receive
};
897 $d->{netin
} += $netdev->{$dev}->{transmit
};
904 sub classify_mountpoint
{
907 return 'device' if $vol =~ m!^/dev/!;
913 my $parse_ct_mountpoint_full = sub {
914 my ($desc, $data, $noerr) = @_;
919 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
921 return undef if $noerr;
925 if (defined(my $size = $res->{size
})) {
926 $size = PVE
::JSONSchema
::parse_size
($size);
927 if (!defined($size)) {
928 return undef if $noerr;
929 die "invalid size: $size\n";
931 $res->{size
} = $size;
934 $res->{type
} = classify_mountpoint
($res->{volume
});
939 sub parse_ct_rootfs
{
940 my ($data, $noerr) = @_;
942 my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
944 $res->{mp
} = '/' if defined($res);
949 sub parse_ct_mountpoint
{
950 my ($data, $noerr) = @_;
952 return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
955 sub print_ct_mountpoint
{
956 my ($info, $nomp) = @_;
957 my $skip = [ 'type' ];
958 push @$skip, 'mp' if $nomp;
959 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
962 sub print_lxc_network
{
964 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
967 sub parse_lxc_network
{
972 return $res if !$data;
974 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
976 $res->{type
} = 'veth';
977 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
982 sub read_cgroup_value
{
983 my ($group, $vmid, $name, $full) = @_;
985 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
987 return PVE
::Tools
::file_get_contents
($path) if $full;
989 return PVE
::Tools
::file_read_firstline
($path);
992 sub write_cgroup_value
{
993 my ($group, $vmid, $name, $value) = @_;
995 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
996 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
1000 sub find_lxc_console_pids
{
1004 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
1007 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
1008 return if !$cmdline;
1010 my @args = split(/\0/, $cmdline);
1012 # serach for lxc-console -n <vmid>
1013 return if scalar(@args) != 3;
1014 return if $args[1] ne '-n';
1015 return if $args[2] !~ m/^\d+$/;
1016 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
1018 my $vmid = $args[2];
1020 push @{$res->{$vmid}}, $pid;
1032 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1034 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1036 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1041 # Note: we cannot use Net:IP, because that only allows strict
1043 sub parse_ipv4_cidr
{
1044 my ($cidr, $noerr) = @_;
1046 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1047 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1050 return undef if $noerr;
1052 die "unable to parse ipv4 address/mask\n";
1058 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1061 sub check_protection
{
1062 my ($vm_conf, $err_msg) = @_;
1064 if ($vm_conf->{protection
}) {
1065 die "$err_msg - protection mode enabled\n";
1069 sub update_lxc_config
{
1070 my ($storage_cfg, $vmid, $conf) = @_;
1072 my $dir = "/var/lib/lxc/$vmid";
1074 if ($conf->{template
}) {
1076 unlink "$dir/config";
1083 die "missing 'arch' - internal error" if !$conf->{arch
};
1084 $raw .= "lxc.arch = $conf->{arch}\n";
1086 my $unprivileged = $conf->{unprivileged
};
1087 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1089 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1090 if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux)$/x) {
1091 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1092 if ($unprivileged || $custom_idmap) {
1093 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
1096 die "implement me (ostype $ostype)";
1099 $raw .= "lxc.monitor.unshare = 1\n";
1101 # Should we read them from /etc/subuid?
1102 if ($unprivileged && !$custom_idmap) {
1103 $raw .= "lxc.id_map = u 0 100000 65536\n";
1104 $raw .= "lxc.id_map = g 0 100000 65536\n";
1107 if (!has_dev_console
($conf)) {
1108 $raw .= "lxc.console = none\n";
1109 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1112 my $ttycount = get_tty_count
($conf);
1113 $raw .= "lxc.tty = $ttycount\n";
1115 # some init scripts expects a linux terminal (turnkey).
1116 $raw .= "lxc.environment = TERM=linux\n";
1118 my $utsname = $conf->{hostname
} || "CT$vmid";
1119 $raw .= "lxc.utsname = $utsname\n";
1121 my $memory = $conf->{memory
} || 512;
1122 my $swap = $conf->{swap
} // 0;
1124 my $lxcmem = int($memory*1024*1024);
1125 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1127 my $lxcswap = int(($memory + $swap)*1024*1024);
1128 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1130 if (my $cpulimit = $conf->{cpulimit
}) {
1131 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1132 my $value = int(100000*$cpulimit);
1133 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1136 my $shares = $conf->{cpuunits
} || 1024;
1137 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1139 my $mountpoint = parse_ct_rootfs
($conf->{rootfs
});
1141 $raw .= "lxc.rootfs = $dir/rootfs\n";
1144 foreach my $k (keys %$conf) {
1145 next if $k !~ m/^net(\d+)$/;
1147 my $d = parse_lxc_network
($conf->{$k});
1149 $raw .= "lxc.network.type = veth\n";
1150 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1151 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1152 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1153 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1156 if (my $lxcconf = $conf->{lxc
}) {
1157 foreach my $entry (@$lxcconf) {
1158 my ($k, $v) = @$entry;
1159 $netcount++ if $k eq 'lxc.network.type';
1160 $raw .= "$k = $v\n";
1164 $raw .= "lxc.network.type = empty\n" if !$netcount;
1166 File
::Path
::mkpath
("$dir/rootfs");
1168 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1171 # verify and cleanup nameserver list (replace \0 with ' ')
1172 sub verify_nameserver_list
{
1173 my ($nameserver_list) = @_;
1176 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1177 PVE
::JSONSchema
::pve_verify_ip
($server);
1178 push @list, $server;
1181 return join(' ', @list);
1184 sub verify_searchdomain_list
{
1185 my ($searchdomain_list) = @_;
1188 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1189 # todo: should we add checks for valid dns domains?
1190 push @list, $server;
1193 return join(' ', @list);
1196 sub add_unused_volume
{
1197 my ($config, $volid) = @_;
1200 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1201 my $test = "unused$ind";
1202 if (my $vid = $config->{$test}) {
1203 return if $vid eq $volid; # do not add duplicates
1209 die "To many unused volume - please delete them first.\n" if !$key;
1211 $config->{$key} = $volid;
1216 sub update_pct_config
{
1217 my ($vmid, $conf, $running, $param, $delete) = @_;
1222 my @deleted_volumes;
1226 my $pid = find_lxc_pid
($vmid);
1227 $rootdir = "/proc/$pid/root";
1230 my $hotplug_error = sub {
1232 push @nohotplug, @_;
1239 if (defined($delete)) {
1240 foreach my $opt (@$delete) {
1241 if (!exists($conf->{$opt})) {
1242 warn "no such option: $opt\n";
1246 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1247 die "unable to delete required option '$opt'\n";
1248 } elsif ($opt eq 'swap') {
1249 delete $conf->{$opt};
1250 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1251 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1252 delete $conf->{$opt};
1253 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1254 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1255 next if $hotplug_error->($opt);
1256 delete $conf->{$opt};
1257 } elsif ($opt =~ m/^net(\d)$/) {
1258 delete $conf->{$opt};
1261 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1262 } elsif ($opt eq 'protection') {
1263 delete $conf->{$opt};
1264 } elsif ($opt =~ m/^unused(\d+)$/) {
1265 next if $hotplug_error->($opt);
1266 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1267 push @deleted_volumes, $conf->{$opt};
1268 delete $conf->{$opt};
1269 } elsif ($opt =~ m/^mp(\d+)$/) {
1270 next if $hotplug_error->($opt);
1271 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1272 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1273 if ($mountpoint->{type
} eq 'volume') {
1274 add_unused_volume
($conf, $mountpoint->{volume
})
1276 delete $conf->{$opt};
1277 } elsif ($opt eq 'unprivileged') {
1278 die "unable to delete read-only option: '$opt'\n";
1280 die "implement me (delete: $opt)"
1282 write_config
($vmid, $conf) if $running;
1286 # There's no separate swap size to configure, there's memory and "total"
1287 # memory (iow. memory+swap). This means we have to change them together.
1288 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1289 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1290 if (defined($wanted_memory) || defined($wanted_swap)) {
1292 $wanted_memory //= ($conf->{memory
} || 512);
1293 $wanted_swap //= ($conf->{swap
} || 0);
1295 my $total = $wanted_memory + $wanted_swap;
1297 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1298 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1300 $conf->{memory
} = $wanted_memory;
1301 $conf->{swap
} = $wanted_swap;
1303 write_config
($vmid, $conf) if $running;
1306 foreach my $opt (keys %$param) {
1307 my $value = $param->{$opt};
1308 if ($opt eq 'hostname') {
1309 $conf->{$opt} = $value;
1310 } elsif ($opt eq 'onboot') {
1311 $conf->{$opt} = $value ?
1 : 0;
1312 } elsif ($opt eq 'startup') {
1313 $conf->{$opt} = $value;
1314 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1315 next if $hotplug_error->($opt);
1316 $conf->{$opt} = $value;
1317 } elsif ($opt eq 'nameserver') {
1318 next if $hotplug_error->($opt);
1319 my $list = verify_nameserver_list
($value);
1320 $conf->{$opt} = $list;
1321 } elsif ($opt eq 'searchdomain') {
1322 next if $hotplug_error->($opt);
1323 my $list = verify_searchdomain_list
($value);
1324 $conf->{$opt} = $list;
1325 } elsif ($opt eq 'cpulimit') {
1326 next if $hotplug_error->($opt); # FIXME: hotplug
1327 $conf->{$opt} = $value;
1328 } elsif ($opt eq 'cpuunits') {
1329 $conf->{$opt} = $value;
1330 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1331 } elsif ($opt eq 'description') {
1332 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1333 } elsif ($opt =~ m/^net(\d+)$/) {
1335 my $net = parse_lxc_network
($value);
1337 $conf->{$opt} = print_lxc_network
($net);
1339 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1341 } elsif ($opt eq 'protection') {
1342 $conf->{$opt} = $value ?
1 : 0;
1343 } elsif ($opt =~ m/^mp(\d+)$/) {
1344 next if $hotplug_error->($opt);
1345 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1346 $conf->{$opt} = $value;
1348 } elsif ($opt eq 'rootfs') {
1349 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1350 die "implement me: $opt";
1351 } elsif ($opt eq 'unprivileged') {
1352 die "unable to modify read-only option: '$opt'\n";
1354 die "implement me: $opt";
1356 write_config
($vmid, $conf) if $running;
1359 if (@deleted_volumes) {
1360 my $storage_cfg = PVE
::Storage
::config
();
1361 foreach my $volume (@deleted_volumes) {
1362 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1367 my $storage_cfg = PVE
::Storage
::config
();
1368 create_disks
($storage_cfg, $vmid, $conf, $conf);
1371 # This should be the last thing we do here
1372 if ($running && scalar(@nohotplug)) {
1373 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1377 sub has_dev_console
{
1380 return !(defined($conf->{console
}) && !$conf->{console
});
1386 return $conf->{tty
} // $confdesc->{tty
}->{default};
1392 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1395 sub get_console_command
{
1396 my ($vmid, $conf) = @_;
1398 my $cmode = get_cmode
($conf);
1400 if ($cmode eq 'console') {
1401 return ['lxc-console', '-n', $vmid, '-t', 0];
1402 } elsif ($cmode eq 'tty') {
1403 return ['lxc-console', '-n', $vmid];
1404 } elsif ($cmode eq 'shell') {
1405 return ['lxc-attach', '--clear-env', '-n', $vmid];
1407 die "internal error";
1411 sub get_primary_ips
{
1414 # return data from net0
1416 return undef if !defined($conf->{net0
});
1417 my $net = parse_lxc_network
($conf->{net0
});
1419 my $ipv4 = $net->{ip
};
1421 if ($ipv4 =~ /^(dhcp|manual)$/) {
1427 my $ipv6 = $net->{ip6
};
1429 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1436 return ($ipv4, $ipv6);
1439 sub delete_mountpoint_volume
{
1440 my ($storage_cfg, $vmid, $volume) = @_;
1442 return if classify_mountpoint
($volume) ne 'volume';
1444 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1445 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1448 sub destroy_lxc_container
{
1449 my ($storage_cfg, $vmid, $conf) = @_;
1451 foreach_mountpoint
($conf, sub {
1452 my ($ms, $mountpoint) = @_;
1453 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1456 rmdir "/var/lib/lxc/$vmid/rootfs";
1457 unlink "/var/lib/lxc/$vmid/config";
1458 rmdir "/var/lib/lxc/$vmid";
1459 destroy_config
($vmid);
1461 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1462 #PVE::Tools::run_command($cmd);
1465 sub vm_stop_cleanup
{
1466 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1471 my $vollist = get_vm_volumes
($conf);
1472 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1475 warn $@ if $@; # avoid errors - just warn
1478 my $safe_num_ne = sub {
1481 return 0 if !defined($a) && !defined($b);
1482 return 1 if !defined($a);
1483 return 1 if !defined($b);
1488 my $safe_string_ne = sub {
1491 return 0 if !defined($a) && !defined($b);
1492 return 1 if !defined($a);
1493 return 1 if !defined($b);
1499 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1501 if ($newnet->{type
} ne 'veth') {
1502 # for when there are physical interfaces
1503 die "cannot update interface of type $newnet->{type}";
1506 my $veth = "veth${vmid}i${netid}";
1507 my $eth = $newnet->{name
};
1509 if (my $oldnetcfg = $conf->{$opt}) {
1510 my $oldnet = parse_lxc_network
($oldnetcfg);
1512 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1513 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1515 PVE
::Network
::veth_delete
($veth);
1516 delete $conf->{$opt};
1517 write_config
($vmid, $conf);
1519 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1521 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1522 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1523 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1525 if ($oldnet->{bridge
}) {
1526 PVE
::Network
::tap_unplug
($veth);
1527 foreach (qw(bridge tag firewall)) {
1528 delete $oldnet->{$_};
1530 $conf->{$opt} = print_lxc_network
($oldnet);
1531 write_config
($vmid, $conf);
1534 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1535 foreach (qw(bridge tag firewall)) {
1536 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1538 $conf->{$opt} = print_lxc_network
($oldnet);
1539 write_config
($vmid, $conf);
1542 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1545 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1549 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1551 my $veth = "veth${vmid}i${netid}";
1552 my $vethpeer = $veth . "p";
1553 my $eth = $newnet->{name
};
1555 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1556 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1558 # attach peer in container
1559 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1560 PVE
::Tools
::run_command
($cmd);
1562 # link up peer in container
1563 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1564 PVE
::Tools
::run_command
($cmd);
1566 my $done = { type
=> 'veth' };
1567 foreach (qw(bridge tag firewall hwaddr name)) {
1568 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1570 $conf->{$opt} = print_lxc_network
($done);
1572 write_config
($vmid, $conf);
1575 sub update_ipconfig
{
1576 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1578 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1580 my $optdata = parse_lxc_network
($conf->{$opt});
1584 my $cmdargs = shift;
1585 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1587 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1589 my $change_ip_config = sub {
1590 my ($ipversion) = @_;
1592 my $family_opt = "-$ipversion";
1593 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1594 my $gw= "gw$suffix";
1595 my $ip= "ip$suffix";
1597 my $newip = $newnet->{$ip};
1598 my $newgw = $newnet->{$gw};
1599 my $oldip = $optdata->{$ip};
1601 my $change_ip = &$safe_string_ne($oldip, $newip);
1602 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1604 return if !$change_ip && !$change_gw;
1606 # step 1: add new IP, if this fails we cancel
1607 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1608 if ($change_ip && $is_real_ip) {
1609 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1616 # step 2: replace gateway
1617 # If this fails we delete the added IP and cancel.
1618 # If it succeeds we save the config and delete the old IP, ignoring
1619 # errors. The config is then saved.
1620 # Note: 'ip route replace' can add
1624 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1625 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1627 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1631 # the route was not replaced, the old IP is still available
1632 # rollback (delete new IP) and cancel
1634 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1635 warn $@ if $@; # no need to die here
1640 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1641 # if the route was not deleted, the guest might have deleted it manually
1647 # from this point on we save the configuration
1648 # step 3: delete old IP ignoring errors
1649 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1650 # We need to enable promote_secondaries, otherwise our newly added
1651 # address will be removed along with the old one.
1654 if ($ipversion == 4) {
1655 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1656 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1657 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1659 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1661 warn $@ if $@; # no need to die here
1663 if ($ipversion == 4) {
1664 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1668 foreach my $property ($ip, $gw) {
1669 if ($newnet->{$property}) {
1670 $optdata->{$property} = $newnet->{$property};
1672 delete $optdata->{$property};
1675 $conf->{$opt} = print_lxc_network
($optdata);
1676 write_config
($vmid, $conf);
1677 $lxc_setup->setup_network($conf);
1680 &$change_ip_config(4);
1681 &$change_ip_config(6);
1685 # Internal snapshots
1687 # NOTE: Snapshot create/delete involves several non-atomic
1688 # action, and can take a long time.
1689 # So we try to avoid locking the file and use 'lock' variable
1690 # inside the config file instead.
1692 my $snapshot_copy_config = sub {
1693 my ($source, $dest) = @_;
1695 foreach my $k (keys %$source) {
1696 next if $k eq 'snapshots';
1697 next if $k eq 'snapstate';
1698 next if $k eq 'snaptime';
1699 next if $k eq 'vmstate';
1700 next if $k eq 'lock';
1701 next if $k eq 'digest';
1702 next if $k eq 'description';
1704 $dest->{$k} = $source->{$k};
1708 my $snapshot_prepare = sub {
1709 my ($vmid, $snapname, $comment) = @_;
1713 my $updatefn = sub {
1715 my $conf = load_config
($vmid);
1717 die "you can't take a snapshot if it's a template\n"
1718 if is_template
($conf);
1722 $conf->{lock} = 'snapshot';
1724 die "snapshot name '$snapname' already used\n"
1725 if defined($conf->{snapshots
}->{$snapname});
1727 my $storecfg = PVE
::Storage
::config
();
1728 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1730 $snap = $conf->{snapshots
}->{$snapname} = {};
1732 &$snapshot_copy_config($conf, $snap);
1734 $snap->{'snapstate'} = "prepare";
1735 $snap->{'snaptime'} = time();
1736 $snap->{'description'} = $comment if $comment;
1737 $conf->{snapshots
}->{$snapname} = $snap;
1739 write_config
($vmid, $conf);
1742 lock_container
($vmid, 10, $updatefn);
1747 my $snapshot_commit = sub {
1748 my ($vmid, $snapname) = @_;
1750 my $updatefn = sub {
1752 my $conf = load_config
($vmid);
1754 die "missing snapshot lock\n"
1755 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1757 die "snapshot '$snapname' does not exist\n"
1758 if !defined($conf->{snapshots
}->{$snapname});
1760 die "wrong snapshot state\n"
1761 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1762 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1764 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1765 delete $conf->{lock};
1766 $conf->{parent
} = $snapname;
1768 write_config
($vmid, $conf);
1771 lock_container
($vmid, 10 ,$updatefn);
1775 my ($feature, $conf, $storecfg, $snapname) = @_;
1779 foreach_mountpoint
($conf, sub {
1780 my ($ms, $mountpoint) = @_;
1782 return if $err; # skip further test
1784 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1786 # TODO: implement support for mountpoints
1787 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1791 return $err ?
0 : 1;
1794 sub snapshot_create
{
1795 my ($vmid, $snapname, $comment) = @_;
1797 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1799 my $conf = load_config
($vmid);
1801 my $running = check_running
($vmid);
1807 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1809 PVE
::Tools
::run_command
(['/bin/sync']);
1812 my $storecfg = PVE
::Storage
::config
();
1813 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1814 my $volid = $rootinfo->{volume
};
1816 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1817 &$snapshot_commit($vmid, $snapname);
1822 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
1827 snapshot_delete
($vmid, $snapname, 1);
1832 sub snapshot_delete
{
1833 my ($vmid, $snapname, $force) = @_;
1839 my $updatefn = sub {
1841 $conf = load_config
($vmid);
1843 die "you can't delete a snapshot if vm is a template\n"
1844 if is_template
($conf);
1846 $snap = $conf->{snapshots
}->{$snapname};
1850 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1852 $snap->{snapstate
} = 'delete';
1854 write_config
($vmid, $conf);
1857 lock_container
($vmid, 10, $updatefn);
1859 my $storecfg = PVE
::Storage
::config
();
1861 my $del_snap = sub {
1865 if ($conf->{parent
} eq $snapname) {
1866 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1867 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1869 delete $conf->{parent
};
1873 delete $conf->{snapshots
}->{$snapname};
1875 write_config
($vmid, $conf);
1878 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1879 my $rootinfo = parse_ct_rootfs
($rootfs);
1880 my $volid = $rootinfo->{volume
};
1883 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1887 if(!$err || ($err && $force)) {
1888 lock_container
($vmid, 10, $del_snap);
1890 die "Can't delete snapshot: $vmid $snapname $err\n";
1895 sub snapshot_rollback
{
1896 my ($vmid, $snapname) = @_;
1898 my $storecfg = PVE
::Storage
::config
();
1900 my $conf = load_config
($vmid);
1902 die "you can't rollback if vm is a template\n" if is_template
($conf);
1904 my $snap = $conf->{snapshots
}->{$snapname};
1906 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1908 my $rootfs = $snap->{rootfs
};
1909 my $rootinfo = parse_ct_rootfs
($rootfs);
1910 my $volid = $rootinfo->{volume
};
1912 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1914 my $updatefn = sub {
1916 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1917 if $snap->{snapstate
};
1921 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1923 die "unable to rollback vm $vmid: vm is running\n"
1924 if check_running
($vmid);
1926 $conf->{lock} = 'rollback';
1930 # copy snapshot config to current config
1932 my $tmp_conf = $conf;
1933 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1934 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1935 delete $conf->{snaptime
};
1936 delete $conf->{snapname
};
1937 $conf->{parent
} = $snapname;
1939 write_config
($vmid, $conf);
1942 my $unlockfn = sub {
1943 delete $conf->{lock};
1944 write_config
($vmid, $conf);
1947 lock_container
($vmid, 10, $updatefn);
1949 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1951 lock_container
($vmid, 5, $unlockfn);
1954 sub template_create
{
1955 my ($vmid, $conf) = @_;
1957 my $storecfg = PVE
::Storage
::config
();
1959 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1960 my $volid = $rootinfo->{volume
};
1962 die "Template feature is not available for '$volid'\n"
1963 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1965 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1967 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1968 $rootinfo->{volume
} = $template_volid;
1969 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1971 write_config
($vmid, $conf);
1977 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1980 sub mountpoint_names
{
1983 my @names = ('rootfs');
1985 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1986 push @names, "mp$i";
1989 return $reverse ?
reverse @names : @names;
1992 # The container might have *different* symlinks than the host. realpath/abs_path
1993 # use the actual filesystem to resolve links.
1994 sub sanitize_mountpoint
{
1996 $mp = '/' . $mp; # we always start with a slash
1997 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1998 $mp =~ s
@/\./@@g; # collapse /./
1999 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
2000 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
2001 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
2005 sub foreach_mountpoint_full
{
2006 my ($conf, $reverse, $func) = @_;
2008 foreach my $key (mountpoint_names
($reverse)) {
2009 my $value = $conf->{$key};
2010 next if !defined($value);
2011 my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs
($value, 1) : parse_ct_mountpoint
($value, 1);
2012 next if !defined($mountpoint);
2014 $mountpoint->{mp
} = sanitize_mountpoint
($mountpoint->{mp
});
2016 my $path = $mountpoint->{volume
};
2017 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
2019 &$func($key, $mountpoint);
2023 sub foreach_mountpoint
{
2024 my ($conf, $func) = @_;
2026 foreach_mountpoint_full
($conf, 0, $func);
2029 sub foreach_mountpoint_reverse
{
2030 my ($conf, $func) = @_;
2032 foreach_mountpoint_full
($conf, 1, $func);
2035 sub check_ct_modify_config_perm
{
2036 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2038 return 1 if $authuser ne 'root@pam';
2040 foreach my $opt (@$key_list) {
2042 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2043 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2044 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2045 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2046 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2047 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2048 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2049 $opt eq 'searchdomain' || $opt eq 'hostname') {
2050 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2052 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2060 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2062 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2063 my $volid_list = get_vm_volumes
($conf);
2065 foreach_mountpoint_reverse
($conf, sub {
2066 my ($ms, $mountpoint) = @_;
2068 my $volid = $mountpoint->{volume
};
2069 my $mount = $mountpoint->{mp
};
2071 return if !$volid || !$mount;
2073 my $mount_path = "$rootdir/$mount";
2074 $mount_path =~ s!/+!/!g;
2076 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2079 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2092 my ($vmid, $storage_cfg, $conf) = @_;
2094 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2095 File
::Path
::make_path
($rootdir);
2097 my $volid_list = get_vm_volumes
($conf);
2098 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2101 foreach_mountpoint
($conf, sub {
2102 my ($ms, $mountpoint) = @_;
2104 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2108 warn "mounting container failed\n";
2109 umount_all
($vmid, $storage_cfg, $conf, 1);
2117 sub mountpoint_mount_path
{
2118 my ($mountpoint, $storage_cfg, $snapname) = @_;
2120 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2123 my $check_mount_path = sub {
2125 $path = File
::Spec-
>canonpath($path);
2126 my $real = Cwd
::realpath
($path);
2127 if ($real ne $path) {
2128 die "mount path modified by symlink: $path != $real";
2137 if ($line =~ m
@^(/dev/loop\d
+):@) {
2141 my $cmd = ['losetup', '--associated', $path];
2142 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2146 # use $rootdir = undef to just return the corresponding mount path
2147 sub mountpoint_mount
{
2148 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2150 my $volid = $mountpoint->{volume
};
2151 my $mount = $mountpoint->{mp
};
2152 my $type = $mountpoint->{type
};
2154 return if !$volid || !$mount;
2158 if (defined($rootdir)) {
2159 $rootdir =~ s!/+$!!;
2160 $mount_path = "$rootdir/$mount";
2161 $mount_path =~ s!/+!/!g;
2162 &$check_mount_path($mount_path);
2163 File
::Path
::mkpath
($mount_path);
2166 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2168 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2172 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2173 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2175 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2176 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2178 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2180 if ($format eq 'subvol') {
2183 if ($scfg->{type
} eq 'zfspool') {
2184 my $path_arg = $path;
2185 $path_arg =~ s!^/+!!;
2186 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2188 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2191 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2194 return wantarray ?
($path, 0) : $path;
2195 } elsif ($format eq 'raw' || $format eq 'iso') {
2196 my $use_loopdev = 0;
2198 if ($scfg->{path
}) {
2199 push @extra_opts, '-o', 'loop';
2201 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' ||
2202 $scfg->{type
} eq 'rbd' || $scfg->{type
} eq 'lvmthin') {
2205 die "unsupported storage type '$scfg->{type}'\n";
2208 if ($format eq 'iso') {
2209 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2210 } elsif ($isBase || defined($snapname)) {
2211 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2213 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2216 return wantarray ?
($path, $use_loopdev) : $path;
2218 die "unsupported image format '$format'\n";
2220 } elsif ($type eq 'device') {
2221 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2222 return wantarray ?
($volid, 0) : $volid;
2223 } elsif ($type eq 'bind') {
2224 die "directory '$volid' does not exist\n" if ! -d
$volid;
2225 &$check_mount_path($volid);
2226 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2227 return wantarray ?
($volid, 0) : $volid;
2230 die "unsupported storage";
2233 sub get_vm_volumes
{
2234 my ($conf, $excludes) = @_;
2238 foreach_mountpoint
($conf, sub {
2239 my ($ms, $mountpoint) = @_;
2241 return if $excludes && $ms eq $excludes;
2243 my $volid = $mountpoint->{volume
};
2245 return if !$volid || $mountpoint->{type
} ne 'volume';
2247 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2250 push @$vollist, $volid;
2257 my ($dev, $rootuid, $rootgid) = @_;
2259 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2260 '-E', "root_owner=$rootuid:$rootgid",
2265 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2267 if ($volid =~ m!^/dev/.+!) {
2272 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2274 die "cannot format volume '$volid' with no storage\n" if !$storage;
2276 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2278 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2280 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2281 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2283 die "cannot format volume '$volid' (format == $format)\n"
2284 if $format ne 'raw';
2286 mkfs
($path, $rootuid, $rootgid);
2290 my ($storecfg, $vollist) = @_;
2292 foreach my $volid (@$vollist) {
2293 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2299 my ($storecfg, $vmid, $settings, $conf) = @_;
2304 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2305 my $chown_vollist = [];
2307 foreach_mountpoint
($settings, sub {
2308 my ($ms, $mountpoint) = @_;
2310 my $volid = $mountpoint->{volume
};
2311 my $mp = $mountpoint->{mp
};
2313 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2315 if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
2316 my ($storeid, $size_gb) = ($1, $2);
2318 my $size_kb = int(${size_gb
}*1024) * 1024;
2320 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2321 # fixme: use better naming ct-$vmid-disk-X.raw?
2323 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2325 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2327 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2329 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2331 push @$chown_vollist, $volid;
2333 } elsif ($scfg->{type
} eq 'zfspool') {
2335 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2337 push @$chown_vollist, $volid;
2338 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'lvmthin') {
2340 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2341 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2343 } elsif ($scfg->{type
} eq 'rbd') {
2345 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2346 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2347 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2349 die "unable to create containers on storage type '$scfg->{type}'\n";
2351 push @$vollist, $volid;
2352 $mountpoint->{volume
} = $volid;
2353 $mountpoint->{size
} = $size_kb * 1024;
2354 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2356 # use specified/existing volid/dir/device
2357 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2361 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2362 foreach my $volid (@$chown_vollist) {
2363 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2364 chown($rootuid, $rootgid, $path);
2366 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2368 # free allocated images on error
2370 destroy_disks
($storecfg, $vollist);
2376 # bash completion helper
2378 sub complete_os_templates
{
2379 my ($cmdname, $pname, $cvalue) = @_;
2381 my $cfg = PVE
::Storage
::config
();
2385 if ($cvalue =~ m/^([^:]+):/) {
2389 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2390 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2393 foreach my $id (keys %$data) {
2394 foreach my $item (@{$data->{$id}}) {
2395 push @$res, $item->{volid
} if defined($item->{volid
});
2402 my $complete_ctid_full = sub {
2405 my $idlist = vmstatus
();
2407 my $active_hash = list_active_containers
();
2411 foreach my $id (keys %$idlist) {
2412 my $d = $idlist->{$id};
2413 if (defined($running)) {
2414 next if $d->{template
};
2415 next if $running && !$active_hash->{$id};
2416 next if !$running && $active_hash->{$id};
2425 return &$complete_ctid_full();
2428 sub complete_ctid_stopped
{
2429 return &$complete_ctid_full(0);
2432 sub complete_ctid_running
{
2433 return &$complete_ctid_full(1);
2443 my $lxc = $conf->{lxc
};
2444 foreach my $entry (@$lxc) {
2445 my ($key, $value) = @$entry;
2446 next if $key ne 'lxc.id_map';
2447 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2448 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2449 push @$id_map, [$type, $ct, $host, $length];
2451 $rootuid = $host if $type eq 'u';
2452 $rootgid = $host if $type eq 'g';
2455 die "failed to parse id_map: $value\n";
2459 if (!@$id_map && $conf->{unprivileged
}) {
2460 # Should we read them from /etc/subuid?
2461 $id_map = [ ['u', '0', '100000', '65536'],
2462 ['g', '0', '100000', '65536'] ];
2463 $rootuid = $rootgid = 100000;
2466 return ($id_map, $rootuid, $rootgid);
2469 sub userns_command
{
2472 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];