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,
234 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
235 'lxc.rootfs.mount' => 1,
236 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
237 ', please use mountpoint options in the "rootfs" key',
241 'lxc.aa_profile' => 1,
242 'lxc.aa_allow_incomplete' => 1,
243 'lxc.se_context' => 1,
246 'lxc.hook.pre-start' => 1,
247 'lxc.hook.pre-mount' => 1,
248 'lxc.hook.mount' => 1,
249 'lxc.hook.start' => 1,
250 'lxc.hook.stop' => 1,
251 'lxc.hook.post-stop' => 1,
252 'lxc.hook.clone' => 1,
253 'lxc.hook.destroy' => 1,
256 'lxc.start.auto' => 1,
257 'lxc.start.delay' => 1,
258 'lxc.start.order' => 1,
260 'lxc.environment' => 1,
267 description
=> "Network interface type.",
272 format_description
=> 'String',
273 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
274 pattern
=> '[-_.\w\d]+',
278 format_description
=> 'vmbr<Number>',
279 description
=> 'Bridge to attach the network device to.',
280 pattern
=> '[-_.\w\d]+',
285 format_description
=> 'MAC',
286 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
287 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
292 format_description
=> 'Number',
293 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
294 minimum
=> 64, # minimum ethernet frame is 64 bytes
299 format
=> 'pve-ipv4-config',
300 format_description
=> 'IPv4Format/CIDR',
301 description
=> 'IPv4 address in CIDR format.',
307 format_description
=> 'GatewayIPv4',
308 description
=> 'Default gateway for IPv4 traffic.',
313 format
=> 'pve-ipv6-config',
314 format_description
=> 'IPv6Format/CIDR',
315 description
=> 'IPv6 address in CIDR format.',
321 format_description
=> 'GatewayIPv6',
322 description
=> 'Default gateway for IPv6 traffic.',
327 format_description
=> '[1|0]',
328 description
=> "Controls whether this interface's firewall rules should be used.",
333 format_description
=> 'VlanNo',
336 description
=> "VLAN tag for this interface.",
341 pattern
=> qr/\d+(?:;\d+)*/,
342 format_description
=> 'vlanid[;vlanid...]',
343 description
=> "VLAN ids to pass through the interface",
347 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
349 my $MAX_LXC_NETWORKS = 10;
350 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
351 $confdesc->{"net$i"} = {
353 type
=> 'string', format
=> $netconf_desc,
354 description
=> "Specifies network interfaces for the container.",
362 format_description
=> 'Path',
363 description
=> 'Path to the mountpoint as seen from inside the container.',
366 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
370 type
=> 'string', format
=> 'pve-volume-id',
371 description
=> "Reference to unused volumes.",
374 my $MAX_MOUNT_POINTS = 10;
375 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
376 $confdesc->{"mp$i"} = {
378 type
=> 'string', format
=> $mp_desc,
379 description
=> "Use volume as container mount point (experimental feature).",
384 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
385 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
386 $confdesc->{"unused$i"} = $unuseddesc;
389 sub write_pct_config
{
390 my ($filename, $conf) = @_;
392 delete $conf->{snapstate
}; # just to be sure
394 my $generate_raw_config = sub {
399 # add description as comment to top of file
400 my $descr = $conf->{description
} || '';
401 foreach my $cl (split(/\n/, $descr)) {
402 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
405 foreach my $key (sort keys %$conf) {
406 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
407 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
408 my $value = $conf->{$key};
409 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
410 $raw .= "$key: $value\n";
413 if (my $lxcconf = $conf->{lxc
}) {
414 foreach my $entry (@$lxcconf) {
415 my ($k, $v) = @$entry;
423 my $raw = &$generate_raw_config($conf);
425 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
426 $raw .= "\n[$snapname]\n";
427 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
434 my ($key, $value) = @_;
436 die "unknown setting '$key'\n" if !$confdesc->{$key};
438 my $type = $confdesc->{$key}->{type
};
440 if (!defined($value)) {
441 die "got undefined value\n";
444 if ($value =~ m/[\n\r]/) {
445 die "property contains a line feed\n";
448 if ($type eq 'boolean') {
449 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
450 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
451 die "type check ('boolean') failed - got '$value'\n";
452 } elsif ($type eq 'integer') {
453 return int($1) if $value =~ m/^(\d+)$/;
454 die "type check ('integer') failed - got '$value'\n";
455 } elsif ($type eq 'number') {
456 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
457 die "type check ('number') failed - got '$value'\n";
458 } elsif ($type eq 'string') {
459 if (my $fmt = $confdesc->{$key}->{format
}) {
460 PVE
::JSONSchema
::check_format
($fmt, $value);
469 sub parse_pct_config
{
470 my ($filename, $raw) = @_;
472 return undef if !defined($raw);
475 digest
=> Digest
::SHA
::sha1_hex
($raw),
479 $filename =~ m
|/lxc/(\d
+).conf
$|
480 || die "got strange filename '$filename'";
488 my @lines = split(/\n/, $raw);
489 foreach my $line (@lines) {
490 next if $line =~ m/^\s*$/;
492 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
494 $conf->{description
} = $descr if $descr;
496 $conf = $res->{snapshots
}->{$section} = {};
500 if ($line =~ m/^\#(.*)\s*$/) {
501 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
505 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
508 if ($valid_lxc_conf_keys->{$key} eq 1 || $key =~ m/^lxc\.cgroup\./) {
509 push @{$conf->{lxc
}}, [$key, $value];
510 } elsif (my $errmsg = $valid_lxc_conf_keys->{$key}) {
511 warn "vm $vmid - $key: $errmsg\n";
513 warn "vm $vmid - unable to parse config: $line\n";
515 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
516 $descr .= PVE
::Tools
::decode_text
($2);
517 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
518 $conf->{snapstate
} = $1;
519 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
522 eval { $value = check_type
($key, $value); };
523 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
524 $conf->{$key} = $value;
526 warn "vm $vmid - unable to parse config: $line\n";
530 $conf->{description
} = $descr if $descr;
532 delete $res->{snapstate
}; # just to be sure
538 my $vmlist = PVE
::Cluster
::get_vmlist
();
540 return $res if !$vmlist || !$vmlist->{ids
};
541 my $ids = $vmlist->{ids
};
543 foreach my $vmid (keys %$ids) {
544 next if !$vmid; # skip CT0
545 my $d = $ids->{$vmid};
546 next if !$d->{node
} || $d->{node
} ne $nodename;
547 next if !$d->{type
} || $d->{type
} ne 'lxc';
548 $res->{$vmid}->{type
} = 'lxc';
553 sub cfs_config_path
{
554 my ($vmid, $node) = @_;
556 $node = $nodename if !$node;
557 return "nodes/$node/lxc/$vmid.conf";
561 my ($vmid, $node) = @_;
563 my $cfspath = cfs_config_path
($vmid, $node);
564 return "/etc/pve/$cfspath";
568 my ($vmid, $node) = @_;
570 $node = $nodename if !$node;
571 my $cfspath = cfs_config_path
($vmid, $node);
573 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
574 die "container $vmid does not exist\n" if !defined($conf);
580 my ($vmid, $conf) = @_;
582 my $dir = "/etc/pve/nodes/$nodename/lxc";
585 write_config
($vmid, $conf);
591 unlink config_file
($vmid, $nodename);
595 my ($vmid, $conf) = @_;
597 my $cfspath = cfs_config_path
($vmid);
599 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
602 # flock: we use one file handle per process, so lock file
603 # can be called multiple times and will succeed for the same process.
605 my $lock_handles = {};
606 my $lockdir = "/run/lock/lxc";
611 return "$lockdir/pve-config-${vmid}.lock";
615 my ($vmid, $timeout) = @_;
617 $timeout = 10 if !$timeout;
620 my $filename = lock_filename
($vmid);
622 mkdir $lockdir if !-d
$lockdir;
624 my $lock_func = sub {
625 if (!$lock_handles->{$$}->{$filename}) {
626 my $fh = new IO
::File
(">>$filename") ||
627 die "can't open file - $!\n";
628 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
631 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
632 print STDERR
"trying to aquire lock...";
635 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
636 # try again on EINTR (see bug #273)
637 if ($success || ($! != EINTR
)) {
642 print STDERR
" failed\n";
643 die "can't aquire lock - $!\n";
646 print STDERR
" OK\n";
649 $lock_handles->{$$}->{$filename}->{refcount
}++;
652 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
655 die "can't lock file '$filename' - $err";
662 my $filename = lock_filename
($vmid);
664 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
665 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
666 if ($refcount <= 0) {
667 $lock_handles->{$$}->{$filename} = undef;
674 my ($vmid, $timeout, $code, @param) = @_;
678 lock_aquire
($vmid, $timeout);
679 eval { $res = &$code(@param) };
691 return defined($confdesc->{$name});
694 # add JSON properties for create and set function
695 sub json_config_properties
{
698 foreach my $opt (keys %$confdesc) {
699 next if $opt eq 'parent' || $opt eq 'snaptime';
700 next if $prop->{$opt};
701 $prop->{$opt} = $confdesc->{$opt};
707 sub json_config_properties_no_rootfs
{
710 foreach my $opt (keys %$confdesc) {
711 next if $prop->{$opt};
712 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
713 $prop->{$opt} = $confdesc->{$opt};
719 # container status helpers
721 sub list_active_containers
{
723 my $filename = "/proc/net/unix";
725 # similar test is used by lcxcontainers.c: list_active_containers
728 my $fh = IO
::File-
>new ($filename, "r");
731 while (defined(my $line = <$fh>)) {
732 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
734 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
745 # warning: this is slow
749 my $active_hash = list_active_containers
();
751 return 1 if defined($active_hash->{$vmid});
756 sub get_container_disk_usage
{
757 my ($vmid, $pid) = @_;
759 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
762 my $last_proc_vmid_stat;
764 my $parse_cpuacct_stat = sub {
767 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
771 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
784 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
786 my $active_hash = list_active_containers
();
788 my $cpucount = $cpuinfo->{cpus
} || 1;
790 my $cdtime = gettimeofday
;
792 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
794 foreach my $vmid (keys %$list) {
795 my $d = $list->{$vmid};
797 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
798 warn $@ if $@; # ignore errors (consider them stopped)
800 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
802 my $cfspath = cfs_config_path
($vmid);
803 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
805 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
806 $d->{name
} =~ s/[\s]//g;
808 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
811 my $res = get_container_disk_usage
($vmid, $d->{pid
});
812 $d->{disk
} = $res->{used
};
813 $d->{maxdisk
} = $res->{total
};
816 # use 4GB by default ??
817 if (my $rootfs = $conf->{rootfs
}) {
818 my $rootinfo = parse_ct_rootfs
($rootfs);
819 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
821 $d->{maxdisk
} = 4*1024*1024*1024;
827 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
828 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
839 $d->{template
} = is_template
($conf);
842 foreach my $vmid (keys %$list) {
843 my $d = $list->{$vmid};
846 next if !$pid; # skip stopped CTs
848 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
849 $d->{uptime
} = time - $ctime; # the method lxcfs uses
851 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
852 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
854 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
855 my @bytes = split(/\n/, $blkio_bytes);
856 foreach my $byte (@bytes) {
857 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
858 $d->{diskread
} = $2 if $key eq 'Read';
859 $d->{diskwrite
} = $2 if $key eq 'Write';
863 my $pstat = &$parse_cpuacct_stat($vmid);
865 my $used = $pstat->{utime} + $pstat->{stime
};
867 my $old = $last_proc_vmid_stat->{$vmid};
869 $last_proc_vmid_stat->{$vmid} = {
877 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
880 my $dutime = $used - $old->{used
};
882 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
883 $last_proc_vmid_stat->{$vmid} = {
889 $d->{cpu
} = $old->{cpu
};
893 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
895 foreach my $dev (keys %$netdev) {
896 next if $dev !~ m/^veth([1-9]\d*)i/;
898 my $d = $list->{$vmid};
902 $d->{netout
} += $netdev->{$dev}->{receive
};
903 $d->{netin
} += $netdev->{$dev}->{transmit
};
910 sub classify_mountpoint
{
913 return 'device' if $vol =~ m!^/dev/!;
919 my $parse_ct_mountpoint_full = sub {
920 my ($desc, $data, $noerr) = @_;
925 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
927 return undef if $noerr;
931 if (defined(my $size = $res->{size
})) {
932 $size = PVE
::JSONSchema
::parse_size
($size);
933 if (!defined($size)) {
934 return undef if $noerr;
935 die "invalid size: $size\n";
937 $res->{size
} = $size;
940 $res->{type
} = classify_mountpoint
($res->{volume
});
945 sub parse_ct_rootfs
{
946 my ($data, $noerr) = @_;
948 my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
950 $res->{mp
} = '/' if defined($res);
955 sub parse_ct_mountpoint
{
956 my ($data, $noerr) = @_;
958 return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
961 sub print_ct_mountpoint
{
962 my ($info, $nomp) = @_;
963 my $skip = [ 'type' ];
964 push @$skip, 'mp' if $nomp;
965 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
968 sub print_lxc_network
{
970 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
973 sub parse_lxc_network
{
978 return $res if !$data;
980 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
982 $res->{type
} = 'veth';
983 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
988 sub read_cgroup_value
{
989 my ($group, $vmid, $name, $full) = @_;
991 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
993 return PVE
::Tools
::file_get_contents
($path) if $full;
995 return PVE
::Tools
::file_read_firstline
($path);
998 sub write_cgroup_value
{
999 my ($group, $vmid, $name, $value) = @_;
1001 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
1002 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
1006 sub find_lxc_console_pids
{
1010 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
1013 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
1014 return if !$cmdline;
1016 my @args = split(/\0/, $cmdline);
1018 # search for lxc-console -n <vmid>
1019 return if scalar(@args) != 3;
1020 return if $args[1] ne '-n';
1021 return if $args[2] !~ m/^\d+$/;
1022 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
1024 my $vmid = $args[2];
1026 push @{$res->{$vmid}}, $pid;
1038 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1040 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1042 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1047 # Note: we cannot use Net:IP, because that only allows strict
1049 sub parse_ipv4_cidr
{
1050 my ($cidr, $noerr) = @_;
1052 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1053 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1056 return undef if $noerr;
1058 die "unable to parse ipv4 address/mask\n";
1064 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1067 sub check_protection
{
1068 my ($vm_conf, $err_msg) = @_;
1070 if ($vm_conf->{protection
}) {
1071 die "$err_msg - protection mode enabled\n";
1075 sub update_lxc_config
{
1076 my ($storage_cfg, $vmid, $conf) = @_;
1078 my $dir = "/var/lib/lxc/$vmid";
1080 if ($conf->{template
}) {
1082 unlink "$dir/config";
1089 die "missing 'arch' - internal error" if !$conf->{arch
};
1090 $raw .= "lxc.arch = $conf->{arch}\n";
1092 my $unprivileged = $conf->{unprivileged
};
1093 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1095 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1096 if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux)$/x) {
1097 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1098 if ($unprivileged || $custom_idmap) {
1099 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
1102 die "implement me (ostype $ostype)";
1105 $raw .= "lxc.monitor.unshare = 1\n";
1107 # Should we read them from /etc/subuid?
1108 if ($unprivileged && !$custom_idmap) {
1109 $raw .= "lxc.id_map = u 0 100000 65536\n";
1110 $raw .= "lxc.id_map = g 0 100000 65536\n";
1113 if (!has_dev_console
($conf)) {
1114 $raw .= "lxc.console = none\n";
1115 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1118 my $ttycount = get_tty_count
($conf);
1119 $raw .= "lxc.tty = $ttycount\n";
1121 # some init scripts expect a linux terminal (turnkey).
1122 $raw .= "lxc.environment = TERM=linux\n";
1124 my $utsname = $conf->{hostname
} || "CT$vmid";
1125 $raw .= "lxc.utsname = $utsname\n";
1127 my $memory = $conf->{memory
} || 512;
1128 my $swap = $conf->{swap
} // 0;
1130 my $lxcmem = int($memory*1024*1024);
1131 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1133 my $lxcswap = int(($memory + $swap)*1024*1024);
1134 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1136 if (my $cpulimit = $conf->{cpulimit
}) {
1137 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1138 my $value = int(100000*$cpulimit);
1139 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1142 my $shares = $conf->{cpuunits
} || 1024;
1143 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1145 my $mountpoint = parse_ct_rootfs
($conf->{rootfs
});
1147 $raw .= "lxc.rootfs = $dir/rootfs\n";
1150 foreach my $k (keys %$conf) {
1151 next if $k !~ m/^net(\d+)$/;
1153 my $d = parse_lxc_network
($conf->{$k});
1155 $raw .= "lxc.network.type = veth\n";
1156 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1157 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1158 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1159 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1162 if (my $lxcconf = $conf->{lxc
}) {
1163 foreach my $entry (@$lxcconf) {
1164 my ($k, $v) = @$entry;
1165 $netcount++ if $k eq 'lxc.network.type';
1166 $raw .= "$k = $v\n";
1170 $raw .= "lxc.network.type = empty\n" if !$netcount;
1172 File
::Path
::mkpath
("$dir/rootfs");
1174 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1177 # verify and cleanup nameserver list (replace \0 with ' ')
1178 sub verify_nameserver_list
{
1179 my ($nameserver_list) = @_;
1182 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1183 PVE
::JSONSchema
::pve_verify_ip
($server);
1184 push @list, $server;
1187 return join(' ', @list);
1190 sub verify_searchdomain_list
{
1191 my ($searchdomain_list) = @_;
1194 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1195 # todo: should we add checks for valid dns domains?
1196 push @list, $server;
1199 return join(' ', @list);
1202 sub add_unused_volume
{
1203 my ($config, $volid) = @_;
1206 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1207 my $test = "unused$ind";
1208 if (my $vid = $config->{$test}) {
1209 return if $vid eq $volid; # do not add duplicates
1215 die "Too many unused volumes - please delete them first.\n" if !$key;
1217 $config->{$key} = $volid;
1222 sub update_pct_config
{
1223 my ($vmid, $conf, $running, $param, $delete) = @_;
1228 my @deleted_volumes;
1232 my $pid = find_lxc_pid
($vmid);
1233 $rootdir = "/proc/$pid/root";
1236 my $hotplug_error = sub {
1238 push @nohotplug, @_;
1245 if (defined($delete)) {
1246 foreach my $opt (@$delete) {
1247 if (!exists($conf->{$opt})) {
1248 warn "no such option: $opt\n";
1252 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1253 die "unable to delete required option '$opt'\n";
1254 } elsif ($opt eq 'swap') {
1255 delete $conf->{$opt};
1256 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1257 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1258 delete $conf->{$opt};
1259 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1260 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1261 next if $hotplug_error->($opt);
1262 delete $conf->{$opt};
1263 } elsif ($opt =~ m/^net(\d)$/) {
1264 delete $conf->{$opt};
1267 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1268 } elsif ($opt eq 'protection') {
1269 delete $conf->{$opt};
1270 } elsif ($opt =~ m/^unused(\d+)$/) {
1271 next if $hotplug_error->($opt);
1272 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1273 push @deleted_volumes, $conf->{$opt};
1274 delete $conf->{$opt};
1275 } elsif ($opt =~ m/^mp(\d+)$/) {
1276 next if $hotplug_error->($opt);
1277 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1278 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1279 if ($mountpoint->{type
} eq 'volume') {
1280 add_unused_volume
($conf, $mountpoint->{volume
})
1282 delete $conf->{$opt};
1283 } elsif ($opt eq 'unprivileged') {
1284 die "unable to delete read-only option: '$opt'\n";
1286 die "implement me (delete: $opt)"
1288 write_config
($vmid, $conf) if $running;
1292 # There's no separate swap size to configure, there's memory and "total"
1293 # memory (iow. memory+swap). This means we have to change them together.
1294 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1295 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1296 if (defined($wanted_memory) || defined($wanted_swap)) {
1298 $wanted_memory //= ($conf->{memory
} || 512);
1299 $wanted_swap //= ($conf->{swap
} || 0);
1301 my $total = $wanted_memory + $wanted_swap;
1303 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1304 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1306 $conf->{memory
} = $wanted_memory;
1307 $conf->{swap
} = $wanted_swap;
1309 write_config
($vmid, $conf) if $running;
1312 foreach my $opt (keys %$param) {
1313 my $value = $param->{$opt};
1314 if ($opt eq 'hostname') {
1315 $conf->{$opt} = $value;
1316 } elsif ($opt eq 'onboot') {
1317 $conf->{$opt} = $value ?
1 : 0;
1318 } elsif ($opt eq 'startup') {
1319 $conf->{$opt} = $value;
1320 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1321 next if $hotplug_error->($opt);
1322 $conf->{$opt} = $value;
1323 } elsif ($opt eq 'nameserver') {
1324 next if $hotplug_error->($opt);
1325 my $list = verify_nameserver_list
($value);
1326 $conf->{$opt} = $list;
1327 } elsif ($opt eq 'searchdomain') {
1328 next if $hotplug_error->($opt);
1329 my $list = verify_searchdomain_list
($value);
1330 $conf->{$opt} = $list;
1331 } elsif ($opt eq 'cpulimit') {
1332 next if $hotplug_error->($opt); # FIXME: hotplug
1333 $conf->{$opt} = $value;
1334 } elsif ($opt eq 'cpuunits') {
1335 $conf->{$opt} = $value;
1336 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1337 } elsif ($opt eq 'description') {
1338 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1339 } elsif ($opt =~ m/^net(\d+)$/) {
1341 my $net = parse_lxc_network
($value);
1343 $conf->{$opt} = print_lxc_network
($net);
1345 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1347 } elsif ($opt eq 'protection') {
1348 $conf->{$opt} = $value ?
1 : 0;
1349 } elsif ($opt =~ m/^mp(\d+)$/) {
1350 next if $hotplug_error->($opt);
1351 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1352 $conf->{$opt} = $value;
1354 } elsif ($opt eq 'rootfs') {
1355 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1356 die "implement me: $opt";
1357 } elsif ($opt eq 'unprivileged') {
1358 die "unable to modify read-only option: '$opt'\n";
1360 die "implement me: $opt";
1362 write_config
($vmid, $conf) if $running;
1365 if (@deleted_volumes) {
1366 my $storage_cfg = PVE
::Storage
::config
();
1367 foreach my $volume (@deleted_volumes) {
1368 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1373 my $storage_cfg = PVE
::Storage
::config
();
1374 create_disks
($storage_cfg, $vmid, $conf, $conf);
1377 # This should be the last thing we do here
1378 if ($running && scalar(@nohotplug)) {
1379 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1383 sub has_dev_console
{
1386 return !(defined($conf->{console
}) && !$conf->{console
});
1392 return $conf->{tty
} // $confdesc->{tty
}->{default};
1398 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1401 sub get_console_command
{
1402 my ($vmid, $conf) = @_;
1404 my $cmode = get_cmode
($conf);
1406 if ($cmode eq 'console') {
1407 return ['lxc-console', '-n', $vmid, '-t', 0];
1408 } elsif ($cmode eq 'tty') {
1409 return ['lxc-console', '-n', $vmid];
1410 } elsif ($cmode eq 'shell') {
1411 return ['lxc-attach', '--clear-env', '-n', $vmid];
1413 die "internal error";
1417 sub get_primary_ips
{
1420 # return data from net0
1422 return undef if !defined($conf->{net0
});
1423 my $net = parse_lxc_network
($conf->{net0
});
1425 my $ipv4 = $net->{ip
};
1427 if ($ipv4 =~ /^(dhcp|manual)$/) {
1433 my $ipv6 = $net->{ip6
};
1435 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1442 return ($ipv4, $ipv6);
1445 sub delete_mountpoint_volume
{
1446 my ($storage_cfg, $vmid, $volume) = @_;
1448 return if classify_mountpoint
($volume) ne 'volume';
1450 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1451 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1454 sub destroy_lxc_container
{
1455 my ($storage_cfg, $vmid, $conf) = @_;
1457 foreach_mountpoint
($conf, sub {
1458 my ($ms, $mountpoint) = @_;
1459 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1462 rmdir "/var/lib/lxc/$vmid/rootfs";
1463 unlink "/var/lib/lxc/$vmid/config";
1464 rmdir "/var/lib/lxc/$vmid";
1465 destroy_config
($vmid);
1467 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1468 #PVE::Tools::run_command($cmd);
1471 sub vm_stop_cleanup
{
1472 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1477 my $vollist = get_vm_volumes
($conf);
1478 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1481 warn $@ if $@; # avoid errors - just warn
1484 my $safe_num_ne = sub {
1487 return 0 if !defined($a) && !defined($b);
1488 return 1 if !defined($a);
1489 return 1 if !defined($b);
1494 my $safe_string_ne = sub {
1497 return 0 if !defined($a) && !defined($b);
1498 return 1 if !defined($a);
1499 return 1 if !defined($b);
1505 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1507 if ($newnet->{type
} ne 'veth') {
1508 # for when there are physical interfaces
1509 die "cannot update interface of type $newnet->{type}";
1512 my $veth = "veth${vmid}i${netid}";
1513 my $eth = $newnet->{name
};
1515 if (my $oldnetcfg = $conf->{$opt}) {
1516 my $oldnet = parse_lxc_network
($oldnetcfg);
1518 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1519 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1521 PVE
::Network
::veth_delete
($veth);
1522 delete $conf->{$opt};
1523 write_config
($vmid, $conf);
1525 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1527 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1528 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1529 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1531 if ($oldnet->{bridge
}) {
1532 PVE
::Network
::tap_unplug
($veth);
1533 foreach (qw(bridge tag firewall)) {
1534 delete $oldnet->{$_};
1536 $conf->{$opt} = print_lxc_network
($oldnet);
1537 write_config
($vmid, $conf);
1540 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1541 foreach (qw(bridge tag firewall)) {
1542 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1544 $conf->{$opt} = print_lxc_network
($oldnet);
1545 write_config
($vmid, $conf);
1548 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1551 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1555 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1557 my $veth = "veth${vmid}i${netid}";
1558 my $vethpeer = $veth . "p";
1559 my $eth = $newnet->{name
};
1561 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1562 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1564 # attach peer in container
1565 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1566 PVE
::Tools
::run_command
($cmd);
1568 # link up peer in container
1569 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1570 PVE
::Tools
::run_command
($cmd);
1572 my $done = { type
=> 'veth' };
1573 foreach (qw(bridge tag firewall hwaddr name)) {
1574 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1576 $conf->{$opt} = print_lxc_network
($done);
1578 write_config
($vmid, $conf);
1581 sub update_ipconfig
{
1582 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1584 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1586 my $optdata = parse_lxc_network
($conf->{$opt});
1590 my $cmdargs = shift;
1591 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1593 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1595 my $change_ip_config = sub {
1596 my ($ipversion) = @_;
1598 my $family_opt = "-$ipversion";
1599 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1600 my $gw= "gw$suffix";
1601 my $ip= "ip$suffix";
1603 my $newip = $newnet->{$ip};
1604 my $newgw = $newnet->{$gw};
1605 my $oldip = $optdata->{$ip};
1607 my $change_ip = &$safe_string_ne($oldip, $newip);
1608 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1610 return if !$change_ip && !$change_gw;
1612 # step 1: add new IP, if this fails we cancel
1613 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1614 if ($change_ip && $is_real_ip) {
1615 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1622 # step 2: replace gateway
1623 # If this fails we delete the added IP and cancel.
1624 # If it succeeds we save the config and delete the old IP, ignoring
1625 # errors. The config is then saved.
1626 # Note: 'ip route replace' can add
1630 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1631 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1633 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1637 # the route was not replaced, the old IP is still available
1638 # rollback (delete new IP) and cancel
1640 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1641 warn $@ if $@; # no need to die here
1646 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1647 # if the route was not deleted, the guest might have deleted it manually
1653 # from this point on we save the configuration
1654 # step 3: delete old IP ignoring errors
1655 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1656 # We need to enable promote_secondaries, otherwise our newly added
1657 # address will be removed along with the old one.
1660 if ($ipversion == 4) {
1661 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1662 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1663 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1665 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1667 warn $@ if $@; # no need to die here
1669 if ($ipversion == 4) {
1670 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1674 foreach my $property ($ip, $gw) {
1675 if ($newnet->{$property}) {
1676 $optdata->{$property} = $newnet->{$property};
1678 delete $optdata->{$property};
1681 $conf->{$opt} = print_lxc_network
($optdata);
1682 write_config
($vmid, $conf);
1683 $lxc_setup->setup_network($conf);
1686 &$change_ip_config(4);
1687 &$change_ip_config(6);
1691 # Internal snapshots
1693 # NOTE: Snapshot create/delete involves several non-atomic
1694 # actions, and can take a long time.
1695 # So we try to avoid locking the file and use the 'lock' variable
1696 # inside the config file instead.
1698 my $snapshot_copy_config = sub {
1699 my ($source, $dest) = @_;
1701 foreach my $k (keys %$source) {
1702 next if $k eq 'snapshots';
1703 next if $k eq 'snapstate';
1704 next if $k eq 'snaptime';
1705 next if $k eq 'vmstate';
1706 next if $k eq 'lock';
1707 next if $k eq 'digest';
1708 next if $k eq 'description';
1710 $dest->{$k} = $source->{$k};
1714 my $snapshot_prepare = sub {
1715 my ($vmid, $snapname, $comment) = @_;
1719 my $updatefn = sub {
1721 my $conf = load_config
($vmid);
1723 die "you can't take a snapshot if it's a template\n"
1724 if is_template
($conf);
1728 $conf->{lock} = 'snapshot';
1730 die "snapshot name '$snapname' already used\n"
1731 if defined($conf->{snapshots
}->{$snapname});
1733 my $storecfg = PVE
::Storage
::config
();
1734 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1736 $snap = $conf->{snapshots
}->{$snapname} = {};
1738 &$snapshot_copy_config($conf, $snap);
1740 $snap->{'snapstate'} = "prepare";
1741 $snap->{'snaptime'} = time();
1742 $snap->{'description'} = $comment if $comment;
1743 $conf->{snapshots
}->{$snapname} = $snap;
1745 write_config
($vmid, $conf);
1748 lock_container
($vmid, 10, $updatefn);
1753 my $snapshot_commit = sub {
1754 my ($vmid, $snapname) = @_;
1756 my $updatefn = sub {
1758 my $conf = load_config
($vmid);
1760 die "missing snapshot lock\n"
1761 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1763 die "snapshot '$snapname' does not exist\n"
1764 if !defined($conf->{snapshots
}->{$snapname});
1766 die "wrong snapshot state\n"
1767 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1768 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1770 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1771 delete $conf->{lock};
1772 $conf->{parent
} = $snapname;
1774 write_config
($vmid, $conf);
1777 lock_container
($vmid, 10 ,$updatefn);
1781 my ($feature, $conf, $storecfg, $snapname) = @_;
1785 foreach_mountpoint
($conf, sub {
1786 my ($ms, $mountpoint) = @_;
1788 return if $err; # skip further test
1790 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1792 # TODO: implement support for mountpoints
1793 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1797 return $err ?
0 : 1;
1800 sub snapshot_create
{
1801 my ($vmid, $snapname, $comment) = @_;
1803 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1805 my $conf = load_config
($vmid);
1807 my $running = check_running
($vmid);
1813 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1815 PVE
::Tools
::run_command
(['/bin/sync']);
1818 my $storecfg = PVE
::Storage
::config
();
1819 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1820 my $volid = $rootinfo->{volume
};
1822 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1823 &$snapshot_commit($vmid, $snapname);
1828 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
1833 snapshot_delete
($vmid, $snapname, 1);
1838 sub snapshot_delete
{
1839 my ($vmid, $snapname, $force) = @_;
1845 my $updatefn = sub {
1847 $conf = load_config
($vmid);
1849 die "you can't delete a snapshot if vm is a template\n"
1850 if is_template
($conf);
1852 $snap = $conf->{snapshots
}->{$snapname};
1856 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1858 $snap->{snapstate
} = 'delete';
1860 write_config
($vmid, $conf);
1863 lock_container
($vmid, 10, $updatefn);
1865 my $storecfg = PVE
::Storage
::config
();
1867 my $unlink_parent = sub {
1869 my ($confref, $new_parent) = @_;
1871 if ($confref->{parent
} && $confref->{parent
} eq $snapname) {
1873 $confref->{parent
} = $new_parent;
1875 delete $confref->{parent
};
1880 my $del_snap = sub {
1884 my $parent = $conf->{snapshots
}->{$snapname}->{parent
};
1885 foreach my $snapkey (keys %{$conf->{snapshots
}}) {
1886 &$unlink_parent($conf->{snapshots
}->{$snapkey}, $parent);
1889 &$unlink_parent($conf, $parent);
1891 delete $conf->{snapshots
}->{$snapname};
1893 write_config
($vmid, $conf);
1896 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1897 my $rootinfo = parse_ct_rootfs
($rootfs);
1898 my $volid = $rootinfo->{volume
};
1901 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1905 if(!$err || ($err && $force)) {
1906 lock_container
($vmid, 10, $del_snap);
1908 die "Can't delete snapshot: $vmid $snapname $err\n";
1913 sub snapshot_rollback
{
1914 my ($vmid, $snapname) = @_;
1916 my $storecfg = PVE
::Storage
::config
();
1918 my $conf = load_config
($vmid);
1920 die "you can't rollback if vm is a template\n" if is_template
($conf);
1922 my $snap = $conf->{snapshots
}->{$snapname};
1924 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1926 my $rootfs = $snap->{rootfs
};
1927 my $rootinfo = parse_ct_rootfs
($rootfs);
1928 my $volid = $rootinfo->{volume
};
1930 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1932 my $updatefn = sub {
1934 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1935 if $snap->{snapstate
};
1939 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1941 die "unable to rollback vm $vmid: vm is running\n"
1942 if check_running
($vmid);
1944 $conf->{lock} = 'rollback';
1948 # copy snapshot config to current config
1950 my $tmp_conf = $conf;
1951 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1952 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1953 delete $conf->{snaptime
};
1954 delete $conf->{snapname
};
1955 $conf->{parent
} = $snapname;
1957 write_config
($vmid, $conf);
1960 my $unlockfn = sub {
1961 delete $conf->{lock};
1962 write_config
($vmid, $conf);
1965 lock_container
($vmid, 10, $updatefn);
1967 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1969 lock_container
($vmid, 5, $unlockfn);
1972 sub template_create
{
1973 my ($vmid, $conf) = @_;
1975 my $storecfg = PVE
::Storage
::config
();
1977 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1978 my $volid = $rootinfo->{volume
};
1980 die "Template feature is not available for '$volid'\n"
1981 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1983 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1985 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1986 $rootinfo->{volume
} = $template_volid;
1987 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1989 write_config
($vmid, $conf);
1995 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1998 sub mountpoint_names
{
2001 my @names = ('rootfs');
2003 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
2004 push @names, "mp$i";
2007 return $reverse ?
reverse @names : @names;
2010 # The container might have *different* symlinks than the host. realpath/abs_path
2011 # use the actual filesystem to resolve links.
2012 sub sanitize_mountpoint
{
2014 $mp = '/' . $mp; # we always start with a slash
2015 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
2016 $mp =~ s
@/\./@@g; # collapse /./
2017 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
2018 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
2019 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
2023 sub foreach_mountpoint_full
{
2024 my ($conf, $reverse, $func) = @_;
2026 foreach my $key (mountpoint_names
($reverse)) {
2027 my $value = $conf->{$key};
2028 next if !defined($value);
2029 my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs
($value, 1) : parse_ct_mountpoint
($value, 1);
2030 next if !defined($mountpoint);
2032 $mountpoint->{mp
} = sanitize_mountpoint
($mountpoint->{mp
});
2034 my $path = $mountpoint->{volume
};
2035 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
2037 &$func($key, $mountpoint);
2041 sub foreach_mountpoint
{
2042 my ($conf, $func) = @_;
2044 foreach_mountpoint_full
($conf, 0, $func);
2047 sub foreach_mountpoint_reverse
{
2048 my ($conf, $func) = @_;
2050 foreach_mountpoint_full
($conf, 1, $func);
2053 sub check_ct_modify_config_perm
{
2054 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2056 return 1 if $authuser ne 'root@pam';
2058 foreach my $opt (@$key_list) {
2060 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2061 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2062 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2063 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2064 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2065 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2066 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2067 $opt eq 'searchdomain' || $opt eq 'hostname') {
2068 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2070 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2078 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2080 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2081 my $volid_list = get_vm_volumes
($conf);
2083 foreach_mountpoint_reverse
($conf, sub {
2084 my ($ms, $mountpoint) = @_;
2086 my $volid = $mountpoint->{volume
};
2087 my $mount = $mountpoint->{mp
};
2089 return if !$volid || !$mount;
2091 my $mount_path = "$rootdir/$mount";
2092 $mount_path =~ s!/+!/!g;
2094 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2097 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2110 my ($vmid, $storage_cfg, $conf) = @_;
2112 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2113 File
::Path
::make_path
($rootdir);
2115 my $volid_list = get_vm_volumes
($conf);
2116 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2119 foreach_mountpoint
($conf, sub {
2120 my ($ms, $mountpoint) = @_;
2122 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2126 warn "mounting container failed\n";
2127 umount_all
($vmid, $storage_cfg, $conf, 1);
2135 sub mountpoint_mount_path
{
2136 my ($mountpoint, $storage_cfg, $snapname) = @_;
2138 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2141 my $check_mount_path = sub {
2143 $path = File
::Spec-
>canonpath($path);
2144 my $real = Cwd
::realpath
($path);
2145 if ($real ne $path) {
2146 die "mount path modified by symlink: $path != $real";
2155 if ($line =~ m
@^(/dev/loop\d
+):@) {
2159 my $cmd = ['losetup', '--associated', $path];
2160 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2164 # use $rootdir = undef to just return the corresponding mount path
2165 sub mountpoint_mount
{
2166 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2168 my $volid = $mountpoint->{volume
};
2169 my $mount = $mountpoint->{mp
};
2170 my $type = $mountpoint->{type
};
2172 return if !$volid || !$mount;
2176 if (defined($rootdir)) {
2177 $rootdir =~ s!/+$!!;
2178 $mount_path = "$rootdir/$mount";
2179 $mount_path =~ s!/+!/!g;
2180 &$check_mount_path($mount_path);
2181 File
::Path
::mkpath
($mount_path);
2184 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2186 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2190 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2191 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2193 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2194 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2196 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2198 if ($format eq 'subvol') {
2201 if ($scfg->{type
} eq 'zfspool') {
2202 my $path_arg = $path;
2203 $path_arg =~ s!^/+!!;
2204 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2206 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2209 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2212 return wantarray ?
($path, 0) : $path;
2213 } elsif ($format eq 'raw' || $format eq 'iso') {
2214 my $use_loopdev = 0;
2216 if ($scfg->{path
}) {
2217 push @extra_opts, '-o', 'loop';
2219 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' ||
2220 $scfg->{type
} eq 'rbd' || $scfg->{type
} eq 'lvmthin') {
2223 die "unsupported storage type '$scfg->{type}'\n";
2226 if ($format eq 'iso') {
2227 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2228 } elsif ($isBase || defined($snapname)) {
2229 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2231 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2234 return wantarray ?
($path, $use_loopdev) : $path;
2236 die "unsupported image format '$format'\n";
2238 } elsif ($type eq 'device') {
2239 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2240 return wantarray ?
($volid, 0) : $volid;
2241 } elsif ($type eq 'bind') {
2242 die "directory '$volid' does not exist\n" if ! -d
$volid;
2243 &$check_mount_path($volid);
2244 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2245 return wantarray ?
($volid, 0) : $volid;
2248 die "unsupported storage";
2251 sub get_vm_volumes
{
2252 my ($conf, $excludes) = @_;
2256 foreach_mountpoint
($conf, sub {
2257 my ($ms, $mountpoint) = @_;
2259 return if $excludes && $ms eq $excludes;
2261 my $volid = $mountpoint->{volume
};
2263 return if !$volid || $mountpoint->{type
} ne 'volume';
2265 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2268 push @$vollist, $volid;
2275 my ($dev, $rootuid, $rootgid) = @_;
2277 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2278 '-E', "root_owner=$rootuid:$rootgid",
2283 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2285 if ($volid =~ m!^/dev/.+!) {
2290 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2292 die "cannot format volume '$volid' with no storage\n" if !$storage;
2294 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2296 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2298 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2299 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2301 die "cannot format volume '$volid' (format == $format)\n"
2302 if $format ne 'raw';
2304 mkfs
($path, $rootuid, $rootgid);
2308 my ($storecfg, $vollist) = @_;
2310 foreach my $volid (@$vollist) {
2311 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2317 my ($storecfg, $vmid, $settings, $conf) = @_;
2322 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2323 my $chown_vollist = [];
2325 foreach_mountpoint
($settings, sub {
2326 my ($ms, $mountpoint) = @_;
2328 my $volid = $mountpoint->{volume
};
2329 my $mp = $mountpoint->{mp
};
2331 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2333 if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
2334 my ($storeid, $size_gb) = ($1, $2);
2336 my $size_kb = int(${size_gb
}*1024) * 1024;
2338 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2339 # fixme: use better naming ct-$vmid-disk-X.raw?
2341 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2343 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2345 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2347 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2349 push @$chown_vollist, $volid;
2351 } elsif ($scfg->{type
} eq 'zfspool') {
2353 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2355 push @$chown_vollist, $volid;
2356 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'lvmthin') {
2358 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2359 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2361 } elsif ($scfg->{type
} eq 'rbd') {
2363 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2364 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2365 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2367 die "unable to create containers on storage type '$scfg->{type}'\n";
2369 push @$vollist, $volid;
2370 $mountpoint->{volume
} = $volid;
2371 $mountpoint->{size
} = $size_kb * 1024;
2372 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2374 # use specified/existing volid/dir/device
2375 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2379 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2380 foreach my $volid (@$chown_vollist) {
2381 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2382 chown($rootuid, $rootgid, $path);
2384 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2386 # free allocated images on error
2388 destroy_disks
($storecfg, $vollist);
2394 # bash completion helper
2396 sub complete_os_templates
{
2397 my ($cmdname, $pname, $cvalue) = @_;
2399 my $cfg = PVE
::Storage
::config
();
2403 if ($cvalue =~ m/^([^:]+):/) {
2407 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2408 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2411 foreach my $id (keys %$data) {
2412 foreach my $item (@{$data->{$id}}) {
2413 push @$res, $item->{volid
} if defined($item->{volid
});
2420 my $complete_ctid_full = sub {
2423 my $idlist = vmstatus
();
2425 my $active_hash = list_active_containers
();
2429 foreach my $id (keys %$idlist) {
2430 my $d = $idlist->{$id};
2431 if (defined($running)) {
2432 next if $d->{template
};
2433 next if $running && !$active_hash->{$id};
2434 next if !$running && $active_hash->{$id};
2443 return &$complete_ctid_full();
2446 sub complete_ctid_stopped
{
2447 return &$complete_ctid_full(0);
2450 sub complete_ctid_running
{
2451 return &$complete_ctid_full(1);
2461 my $lxc = $conf->{lxc
};
2462 foreach my $entry (@$lxc) {
2463 my ($key, $value) = @$entry;
2464 next if $key ne 'lxc.id_map';
2465 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2466 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2467 push @$id_map, [$type, $ct, $host, $length];
2469 $rootuid = $host if $type eq 'u';
2470 $rootgid = $host if $type eq 'g';
2473 die "failed to parse id_map: $value\n";
2477 if (!@$id_map && $conf->{unprivileged
}) {
2478 # Should we read them from /etc/subuid?
2479 $id_map = [ ['u', '0', '100000', '65536'],
2480 ['g', '0', '100000', '65536'] ];
2481 $rootuid = $rootgid = 100000;
2484 return ($id_map, $rootuid, $rootgid);
2487 sub userns_command
{
2490 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];