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
=> 'pve-lxc-mp-string',
42 format_description
=> 'volume',
43 description
=> 'Volume, device or directory to mount into the container.',
47 format_description
=> '[1|0]',
48 description
=> 'Whether to include the mountpoint in backups.',
53 format
=> 'disk-size',
54 format_description
=> 'DiskSize',
55 description
=> 'Volume size (read only value).',
60 format_description
=> 'acl',
61 description
=> 'Explicitly enable or disable ACL support.',
66 format_description
=> 'ro',
67 description
=> 'Read-only mountpoint (not supported with bind mounts)',
72 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
73 type
=> 'string', format
=> $rootfs_desc,
74 description
=> "Use volume as container root.",
78 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
79 description
=> "The name of the snapshot.",
80 type
=> 'string', format
=> 'pve-configid',
88 description
=> "Lock/unlock the VM.",
89 enum
=> [qw(migrate backup snapshot rollback)],
94 description
=> "Specifies whether a VM will be started during system bootup.",
97 startup
=> get_standard_option
('pve-startup-order'),
101 description
=> "Enable/disable Template.",
107 enum
=> ['amd64', 'i386'],
108 description
=> "OS architecture type.",
114 enum
=> ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux'],
115 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
120 description
=> "Attach a console device (/dev/console) to the container.",
126 description
=> "Specify the number of tty available to the container",
134 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.",
142 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.",
150 description
=> "Amount of RAM for the VM in MB.",
157 description
=> "Amount of SWAP for the VM in MB.",
163 description
=> "Set a host name for the container.",
164 type
=> 'string', format
=> 'dns-name',
170 description
=> "Container description. Only used on the configuration web interface.",
174 type
=> 'string', format
=> 'dns-name-list',
175 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
179 type
=> 'string', format
=> 'address-list',
180 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.",
182 rootfs
=> get_standard_option
('pve-ct-rootfs'),
185 type
=> 'string', format
=> 'pve-configid',
187 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
191 description
=> "Timestamp for snapshots.",
197 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).",
199 enum
=> ['shell', 'console', 'tty'],
205 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
211 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
216 my $valid_lxc_conf_keys = {
220 'lxc.haltsignal' => 1,
221 'lxc.rebootsignal' => 1,
222 'lxc.stopsignal' => 1,
224 'lxc.network.type' => 1,
225 'lxc.network.flags' => 1,
226 'lxc.network.link' => 1,
227 'lxc.network.mtu' => 1,
228 'lxc.network.name' => 1,
229 'lxc.network.hwaddr' => 1,
230 'lxc.network.ipv4' => 1,
231 'lxc.network.ipv4.gateway' => 1,
232 'lxc.network.ipv6' => 1,
233 'lxc.network.ipv6.gateway' => 1,
234 'lxc.network.script.up' => 1,
235 'lxc.network.script.down' => 1,
237 'lxc.console.logfile' => 1,
240 'lxc.devttydir' => 1,
241 'lxc.hook.autodev' => 1,
245 'lxc.mount.entry' => 1,
246 'lxc.mount.auto' => 1,
247 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
248 'lxc.rootfs.mount' => 1,
249 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
250 ', please use mountpoint options in the "rootfs" key',
254 'lxc.aa_profile' => 1,
255 'lxc.aa_allow_incomplete' => 1,
256 'lxc.se_context' => 1,
259 'lxc.hook.pre-start' => 1,
260 'lxc.hook.pre-mount' => 1,
261 'lxc.hook.mount' => 1,
262 'lxc.hook.start' => 1,
263 'lxc.hook.stop' => 1,
264 'lxc.hook.post-stop' => 1,
265 'lxc.hook.clone' => 1,
266 'lxc.hook.destroy' => 1,
269 'lxc.start.auto' => 1,
270 'lxc.start.delay' => 1,
271 'lxc.start.order' => 1,
273 'lxc.environment' => 1,
280 description
=> "Network interface type.",
285 format_description
=> 'String',
286 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
287 pattern
=> '[-_.\w\d]+',
291 format_description
=> 'vmbr<Number>',
292 description
=> 'Bridge to attach the network device to.',
293 pattern
=> '[-_.\w\d]+',
298 format_description
=> 'MAC',
299 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
300 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
305 format_description
=> 'Number',
306 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
307 minimum
=> 64, # minimum ethernet frame is 64 bytes
312 format
=> 'pve-ipv4-config',
313 format_description
=> 'IPv4Format/CIDR',
314 description
=> 'IPv4 address in CIDR format.',
320 format_description
=> 'GatewayIPv4',
321 description
=> 'Default gateway for IPv4 traffic.',
326 format
=> 'pve-ipv6-config',
327 format_description
=> 'IPv6Format/CIDR',
328 description
=> 'IPv6 address in CIDR format.',
334 format_description
=> 'GatewayIPv6',
335 description
=> 'Default gateway for IPv6 traffic.',
340 format_description
=> '[1|0]',
341 description
=> "Controls whether this interface's firewall rules should be used.",
346 format_description
=> 'VlanNo',
349 description
=> "VLAN tag for this interface.",
354 pattern
=> qr/\d+(?:;\d+)*/,
355 format_description
=> 'vlanid[;vlanid...]',
356 description
=> "VLAN ids to pass through the interface",
360 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
362 my $MAX_LXC_NETWORKS = 10;
363 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
364 $confdesc->{"net$i"} = {
366 type
=> 'string', format
=> $netconf_desc,
367 description
=> "Specifies network interfaces for the container.",
371 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
372 sub verify_lxc_mp_string
{
373 my ($mp, $noerr) = @_;
377 # /. or /.. at the end
378 # ../ at the beginning
380 if($mp =~ m
@/\.\
.?
/@ ||
383 return undef if $noerr;
384 die "$mp contains illegal character sequences\n";
393 format
=> 'pve-lxc-mp-string',
394 format_description
=> 'Path',
395 description
=> 'Path to the mountpoint as seen from inside the container.',
398 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
402 type
=> 'string', format
=> 'pve-volume-id',
403 description
=> "Reference to unused volumes.",
406 my $MAX_MOUNT_POINTS = 10;
407 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
408 $confdesc->{"mp$i"} = {
410 type
=> 'string', format
=> $mp_desc,
411 description
=> "Use volume as container mount point (experimental feature).",
416 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
417 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
418 $confdesc->{"unused$i"} = $unuseddesc;
421 sub write_pct_config
{
422 my ($filename, $conf) = @_;
424 delete $conf->{snapstate
}; # just to be sure
426 my $generate_raw_config = sub {
431 # add description as comment to top of file
432 my $descr = $conf->{description
} || '';
433 foreach my $cl (split(/\n/, $descr)) {
434 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
437 foreach my $key (sort keys %$conf) {
438 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
439 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
440 my $value = $conf->{$key};
441 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
442 $raw .= "$key: $value\n";
445 if (my $lxcconf = $conf->{lxc
}) {
446 foreach my $entry (@$lxcconf) {
447 my ($k, $v) = @$entry;
455 my $raw = &$generate_raw_config($conf);
457 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
458 $raw .= "\n[$snapname]\n";
459 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
466 my ($key, $value) = @_;
468 die "unknown setting '$key'\n" if !$confdesc->{$key};
470 my $type = $confdesc->{$key}->{type
};
472 if (!defined($value)) {
473 die "got undefined value\n";
476 if ($value =~ m/[\n\r]/) {
477 die "property contains a line feed\n";
480 if ($type eq 'boolean') {
481 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
482 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
483 die "type check ('boolean') failed - got '$value'\n";
484 } elsif ($type eq 'integer') {
485 return int($1) if $value =~ m/^(\d+)$/;
486 die "type check ('integer') failed - got '$value'\n";
487 } elsif ($type eq 'number') {
488 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
489 die "type check ('number') failed - got '$value'\n";
490 } elsif ($type eq 'string') {
491 if (my $fmt = $confdesc->{$key}->{format
}) {
492 PVE
::JSONSchema
::check_format
($fmt, $value);
501 sub parse_pct_config
{
502 my ($filename, $raw) = @_;
504 return undef if !defined($raw);
507 digest
=> Digest
::SHA
::sha1_hex
($raw),
511 $filename =~ m
|/lxc/(\d
+).conf
$|
512 || die "got strange filename '$filename'";
520 my @lines = split(/\n/, $raw);
521 foreach my $line (@lines) {
522 next if $line =~ m/^\s*$/;
524 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
526 $conf->{description
} = $descr if $descr;
528 $conf = $res->{snapshots
}->{$section} = {};
532 if ($line =~ m/^\#(.*)\s*$/) {
533 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
537 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
540 my $validity = $valid_lxc_conf_keys->{$key} || 0;
541 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
542 push @{$conf->{lxc
}}, [$key, $value];
543 } elsif (my $errmsg = $validity) {
544 warn "vm $vmid - $key: $errmsg\n";
546 warn "vm $vmid - unable to parse config: $line\n";
548 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
549 $descr .= PVE
::Tools
::decode_text
($2);
550 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
551 $conf->{snapstate
} = $1;
552 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
555 eval { $value = check_type
($key, $value); };
556 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
557 $conf->{$key} = $value;
559 warn "vm $vmid - unable to parse config: $line\n";
563 $conf->{description
} = $descr if $descr;
565 delete $res->{snapstate
}; # just to be sure
571 my $vmlist = PVE
::Cluster
::get_vmlist
();
573 return $res if !$vmlist || !$vmlist->{ids
};
574 my $ids = $vmlist->{ids
};
576 foreach my $vmid (keys %$ids) {
577 next if !$vmid; # skip CT0
578 my $d = $ids->{$vmid};
579 next if !$d->{node
} || $d->{node
} ne $nodename;
580 next if !$d->{type
} || $d->{type
} ne 'lxc';
581 $res->{$vmid}->{type
} = 'lxc';
586 sub cfs_config_path
{
587 my ($vmid, $node) = @_;
589 $node = $nodename if !$node;
590 return "nodes/$node/lxc/$vmid.conf";
594 my ($vmid, $node) = @_;
596 my $cfspath = cfs_config_path
($vmid, $node);
597 return "/etc/pve/$cfspath";
601 my ($vmid, $node) = @_;
603 $node = $nodename if !$node;
604 my $cfspath = cfs_config_path
($vmid, $node);
606 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
607 die "container $vmid does not exist\n" if !defined($conf);
613 my ($vmid, $conf) = @_;
615 my $dir = "/etc/pve/nodes/$nodename/lxc";
618 write_config
($vmid, $conf);
624 unlink config_file
($vmid, $nodename);
628 my ($vmid, $conf) = @_;
630 my $cfspath = cfs_config_path
($vmid);
632 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
635 # flock: we use one file handle per process, so lock file
636 # can be called multiple times and will succeed for the same process.
638 my $lock_handles = {};
639 my $lockdir = "/run/lock/lxc";
644 return "$lockdir/pve-config-${vmid}.lock";
648 my ($vmid, $timeout, $code, @param) = @_;
650 $timeout = 10 if !$timeout;
652 my $filename = lock_filename
($vmid);
654 mkdir $lockdir if !-d
$lockdir;
656 my $res = PVE
::Tools
::lock_file_full
($filename, $timeout, 0, $code, @param);
666 return defined($confdesc->{$name});
669 # add JSON properties for create and set function
670 sub json_config_properties
{
673 foreach my $opt (keys %$confdesc) {
674 next if $opt eq 'parent' || $opt eq 'snaptime';
675 next if $prop->{$opt};
676 $prop->{$opt} = $confdesc->{$opt};
682 sub json_config_properties_no_rootfs
{
685 foreach my $opt (keys %$confdesc) {
686 next if $prop->{$opt};
687 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
688 $prop->{$opt} = $confdesc->{$opt};
694 # container status helpers
696 sub list_active_containers
{
698 my $filename = "/proc/net/unix";
700 # similar test is used by lcxcontainers.c: list_active_containers
703 my $fh = IO
::File-
>new ($filename, "r");
706 while (defined(my $line = <$fh>)) {
707 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
709 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
720 # warning: this is slow
724 my $active_hash = list_active_containers
();
726 return 1 if defined($active_hash->{$vmid});
731 sub get_container_disk_usage
{
732 my ($vmid, $pid) = @_;
734 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
737 my $last_proc_vmid_stat;
739 my $parse_cpuacct_stat = sub {
742 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
746 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
759 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
761 my $active_hash = list_active_containers
();
763 my $cpucount = $cpuinfo->{cpus
} || 1;
765 my $cdtime = gettimeofday
;
767 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
769 foreach my $vmid (keys %$list) {
770 my $d = $list->{$vmid};
772 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
773 warn $@ if $@; # ignore errors (consider them stopped)
775 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
777 my $cfspath = cfs_config_path
($vmid);
778 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
780 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
781 $d->{name
} =~ s/[\s]//g;
783 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
786 my $res = get_container_disk_usage
($vmid, $d->{pid
});
787 $d->{disk
} = $res->{used
};
788 $d->{maxdisk
} = $res->{total
};
791 # use 4GB by default ??
792 if (my $rootfs = $conf->{rootfs
}) {
793 my $rootinfo = parse_ct_rootfs
($rootfs);
794 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
796 $d->{maxdisk
} = 4*1024*1024*1024;
802 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
803 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
814 $d->{template
} = is_template
($conf);
817 foreach my $vmid (keys %$list) {
818 my $d = $list->{$vmid};
821 next if !$pid; # skip stopped CTs
823 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
824 $d->{uptime
} = time - $ctime; # the method lxcfs uses
826 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
827 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
829 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
830 my @bytes = split(/\n/, $blkio_bytes);
831 foreach my $byte (@bytes) {
832 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
833 $d->{diskread
} = $2 if $key eq 'Read';
834 $d->{diskwrite
} = $2 if $key eq 'Write';
838 my $pstat = &$parse_cpuacct_stat($vmid);
840 my $used = $pstat->{utime} + $pstat->{stime
};
842 my $old = $last_proc_vmid_stat->{$vmid};
844 $last_proc_vmid_stat->{$vmid} = {
852 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
855 my $dutime = $used - $old->{used
};
857 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
858 $last_proc_vmid_stat->{$vmid} = {
864 $d->{cpu
} = $old->{cpu
};
868 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
870 foreach my $dev (keys %$netdev) {
871 next if $dev !~ m/^veth([1-9]\d*)i/;
873 my $d = $list->{$vmid};
877 $d->{netout
} += $netdev->{$dev}->{receive
};
878 $d->{netin
} += $netdev->{$dev}->{transmit
};
885 sub classify_mountpoint
{
888 return 'device' if $vol =~ m!^/dev/!;
894 my $parse_ct_mountpoint_full = sub {
895 my ($desc, $data, $noerr) = @_;
900 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
902 return undef if $noerr;
906 if (defined(my $size = $res->{size
})) {
907 $size = PVE
::JSONSchema
::parse_size
($size);
908 if (!defined($size)) {
909 return undef if $noerr;
910 die "invalid size: $size\n";
912 $res->{size
} = $size;
915 $res->{type
} = classify_mountpoint
($res->{volume
});
920 sub parse_ct_rootfs
{
921 my ($data, $noerr) = @_;
923 my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
925 $res->{mp
} = '/' if defined($res);
930 sub parse_ct_mountpoint
{
931 my ($data, $noerr) = @_;
933 return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
936 sub print_ct_mountpoint
{
937 my ($info, $nomp) = @_;
938 my $skip = [ 'type' ];
939 push @$skip, 'mp' if $nomp;
940 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
943 sub print_lxc_network
{
945 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
948 sub parse_lxc_network
{
953 return $res if !$data;
955 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
957 $res->{type
} = 'veth';
958 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
963 sub read_cgroup_value
{
964 my ($group, $vmid, $name, $full) = @_;
966 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
968 return PVE
::Tools
::file_get_contents
($path) if $full;
970 return PVE
::Tools
::file_read_firstline
($path);
973 sub write_cgroup_value
{
974 my ($group, $vmid, $name, $value) = @_;
976 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
977 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
981 sub find_lxc_console_pids
{
985 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
988 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
991 my @args = split(/\0/, $cmdline);
993 # search for lxc-console -n <vmid>
994 return if scalar(@args) != 3;
995 return if $args[1] ne '-n';
996 return if $args[2] !~ m/^\d+$/;
997 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
1001 push @{$res->{$vmid}}, $pid;
1013 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1015 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1017 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1022 # Note: we cannot use Net:IP, because that only allows strict
1024 sub parse_ipv4_cidr
{
1025 my ($cidr, $noerr) = @_;
1027 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1028 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1031 return undef if $noerr;
1033 die "unable to parse ipv4 address/mask\n";
1039 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1042 sub check_protection
{
1043 my ($vm_conf, $err_msg) = @_;
1045 if ($vm_conf->{protection
}) {
1046 die "$err_msg - protection mode enabled\n";
1050 sub update_lxc_config
{
1051 my ($storage_cfg, $vmid, $conf) = @_;
1053 my $dir = "/var/lib/lxc/$vmid";
1055 if ($conf->{template
}) {
1057 unlink "$dir/config";
1064 die "missing 'arch' - internal error" if !$conf->{arch
};
1065 $raw .= "lxc.arch = $conf->{arch}\n";
1067 my $unprivileged = $conf->{unprivileged
};
1068 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1070 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1071 if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux)$/x) {
1072 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1073 if ($unprivileged || $custom_idmap) {
1074 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
1077 die "implement me (ostype $ostype)";
1080 $raw .= "lxc.monitor.unshare = 1\n";
1082 # Should we read them from /etc/subuid?
1083 if ($unprivileged && !$custom_idmap) {
1084 $raw .= "lxc.id_map = u 0 100000 65536\n";
1085 $raw .= "lxc.id_map = g 0 100000 65536\n";
1088 if (!has_dev_console
($conf)) {
1089 $raw .= "lxc.console = none\n";
1090 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1093 my $ttycount = get_tty_count
($conf);
1094 $raw .= "lxc.tty = $ttycount\n";
1096 # some init scripts expect a linux terminal (turnkey).
1097 $raw .= "lxc.environment = TERM=linux\n";
1099 my $utsname = $conf->{hostname
} || "CT$vmid";
1100 $raw .= "lxc.utsname = $utsname\n";
1102 my $memory = $conf->{memory
} || 512;
1103 my $swap = $conf->{swap
} // 0;
1105 my $lxcmem = int($memory*1024*1024);
1106 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1108 my $lxcswap = int(($memory + $swap)*1024*1024);
1109 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1111 if (my $cpulimit = $conf->{cpulimit
}) {
1112 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1113 my $value = int(100000*$cpulimit);
1114 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1117 my $shares = $conf->{cpuunits
} || 1024;
1118 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1120 my $mountpoint = parse_ct_rootfs
($conf->{rootfs
});
1122 $raw .= "lxc.rootfs = $dir/rootfs\n";
1125 foreach my $k (keys %$conf) {
1126 next if $k !~ m/^net(\d+)$/;
1128 my $d = parse_lxc_network
($conf->{$k});
1130 $raw .= "lxc.network.type = veth\n";
1131 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1132 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1133 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1134 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1137 if (my $lxcconf = $conf->{lxc
}) {
1138 foreach my $entry (@$lxcconf) {
1139 my ($k, $v) = @$entry;
1140 $netcount++ if $k eq 'lxc.network.type';
1141 $raw .= "$k = $v\n";
1145 $raw .= "lxc.network.type = empty\n" if !$netcount;
1147 File
::Path
::mkpath
("$dir/rootfs");
1149 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1152 # verify and cleanup nameserver list (replace \0 with ' ')
1153 sub verify_nameserver_list
{
1154 my ($nameserver_list) = @_;
1157 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1158 PVE
::JSONSchema
::pve_verify_ip
($server);
1159 push @list, $server;
1162 return join(' ', @list);
1165 sub verify_searchdomain_list
{
1166 my ($searchdomain_list) = @_;
1169 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1170 # todo: should we add checks for valid dns domains?
1171 push @list, $server;
1174 return join(' ', @list);
1177 sub add_unused_volume
{
1178 my ($config, $volid) = @_;
1181 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1182 my $test = "unused$ind";
1183 if (my $vid = $config->{$test}) {
1184 return if $vid eq $volid; # do not add duplicates
1190 die "Too many unused volumes - please delete them first.\n" if !$key;
1192 $config->{$key} = $volid;
1197 sub update_pct_config
{
1198 my ($vmid, $conf, $running, $param, $delete) = @_;
1203 my @deleted_volumes;
1207 my $pid = find_lxc_pid
($vmid);
1208 $rootdir = "/proc/$pid/root";
1211 my $hotplug_error = sub {
1213 push @nohotplug, @_;
1220 if (defined($delete)) {
1221 foreach my $opt (@$delete) {
1222 if (!exists($conf->{$opt})) {
1223 warn "no such option: $opt\n";
1227 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1228 die "unable to delete required option '$opt'\n";
1229 } elsif ($opt eq 'swap') {
1230 delete $conf->{$opt};
1231 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1232 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1233 delete $conf->{$opt};
1234 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1235 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1236 next if $hotplug_error->($opt);
1237 delete $conf->{$opt};
1238 } elsif ($opt =~ m/^net(\d)$/) {
1239 delete $conf->{$opt};
1242 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1243 } elsif ($opt eq 'protection') {
1244 delete $conf->{$opt};
1245 } elsif ($opt =~ m/^unused(\d+)$/) {
1246 next if $hotplug_error->($opt);
1247 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1248 push @deleted_volumes, $conf->{$opt};
1249 delete $conf->{$opt};
1250 } elsif ($opt =~ m/^mp(\d+)$/) {
1251 next if $hotplug_error->($opt);
1252 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1253 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1254 if ($mountpoint->{type
} eq 'volume') {
1255 add_unused_volume
($conf, $mountpoint->{volume
})
1257 delete $conf->{$opt};
1258 } elsif ($opt eq 'unprivileged') {
1259 die "unable to delete read-only option: '$opt'\n";
1261 die "implement me (delete: $opt)"
1263 write_config
($vmid, $conf) if $running;
1267 # There's no separate swap size to configure, there's memory and "total"
1268 # memory (iow. memory+swap). This means we have to change them together.
1269 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1270 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1271 if (defined($wanted_memory) || defined($wanted_swap)) {
1273 my $old_memory = ($conf->{memory
} || 512);
1274 my $old_swap = ($conf->{swap
} || 0);
1276 $wanted_memory //= $old_memory;
1277 $wanted_swap //= $old_swap;
1279 my $total = $wanted_memory + $wanted_swap;
1281 my $old_total = $old_memory + $old_swap;
1282 if ($total > $old_total) {
1283 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1284 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1286 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1287 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1290 $conf->{memory
} = $wanted_memory;
1291 $conf->{swap
} = $wanted_swap;
1293 write_config
($vmid, $conf) if $running;
1296 foreach my $opt (keys %$param) {
1297 my $value = $param->{$opt};
1298 if ($opt eq 'hostname') {
1299 $conf->{$opt} = $value;
1300 } elsif ($opt eq 'onboot') {
1301 $conf->{$opt} = $value ?
1 : 0;
1302 } elsif ($opt eq 'startup') {
1303 $conf->{$opt} = $value;
1304 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1305 next if $hotplug_error->($opt);
1306 $conf->{$opt} = $value;
1307 } elsif ($opt eq 'nameserver') {
1308 next if $hotplug_error->($opt);
1309 my $list = verify_nameserver_list
($value);
1310 $conf->{$opt} = $list;
1311 } elsif ($opt eq 'searchdomain') {
1312 next if $hotplug_error->($opt);
1313 my $list = verify_searchdomain_list
($value);
1314 $conf->{$opt} = $list;
1315 } elsif ($opt eq 'cpulimit') {
1316 next if $hotplug_error->($opt); # FIXME: hotplug
1317 $conf->{$opt} = $value;
1318 } elsif ($opt eq 'cpuunits') {
1319 $conf->{$opt} = $value;
1320 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1321 } elsif ($opt eq 'description') {
1322 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1323 } elsif ($opt =~ m/^net(\d+)$/) {
1325 my $net = parse_lxc_network
($value);
1327 $conf->{$opt} = print_lxc_network
($net);
1329 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1331 } elsif ($opt eq 'protection') {
1332 $conf->{$opt} = $value ?
1 : 0;
1333 } elsif ($opt =~ m/^mp(\d+)$/) {
1334 next if $hotplug_error->($opt);
1335 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1336 $conf->{$opt} = $value;
1338 } elsif ($opt eq 'rootfs') {
1339 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1340 die "implement me: $opt";
1341 } elsif ($opt eq 'unprivileged') {
1342 die "unable to modify read-only option: '$opt'\n";
1344 die "implement me: $opt";
1346 write_config
($vmid, $conf) if $running;
1349 if (@deleted_volumes) {
1350 my $storage_cfg = PVE
::Storage
::config
();
1351 foreach my $volume (@deleted_volumes) {
1352 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1357 my $storage_cfg = PVE
::Storage
::config
();
1358 create_disks
($storage_cfg, $vmid, $conf, $conf);
1361 # This should be the last thing we do here
1362 if ($running && scalar(@nohotplug)) {
1363 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1367 sub has_dev_console
{
1370 return !(defined($conf->{console
}) && !$conf->{console
});
1376 return $conf->{tty
} // $confdesc->{tty
}->{default};
1382 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1385 sub get_console_command
{
1386 my ($vmid, $conf) = @_;
1388 my $cmode = get_cmode
($conf);
1390 if ($cmode eq 'console') {
1391 return ['lxc-console', '-n', $vmid, '-t', 0];
1392 } elsif ($cmode eq 'tty') {
1393 return ['lxc-console', '-n', $vmid];
1394 } elsif ($cmode eq 'shell') {
1395 return ['lxc-attach', '--clear-env', '-n', $vmid];
1397 die "internal error";
1401 sub get_primary_ips
{
1404 # return data from net0
1406 return undef if !defined($conf->{net0
});
1407 my $net = parse_lxc_network
($conf->{net0
});
1409 my $ipv4 = $net->{ip
};
1411 if ($ipv4 =~ /^(dhcp|manual)$/) {
1417 my $ipv6 = $net->{ip6
};
1419 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1426 return ($ipv4, $ipv6);
1429 sub delete_mountpoint_volume
{
1430 my ($storage_cfg, $vmid, $volume) = @_;
1432 return if classify_mountpoint
($volume) ne 'volume';
1434 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1435 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1438 sub destroy_lxc_container
{
1439 my ($storage_cfg, $vmid, $conf) = @_;
1441 foreach_mountpoint
($conf, sub {
1442 my ($ms, $mountpoint) = @_;
1443 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1446 rmdir "/var/lib/lxc/$vmid/rootfs";
1447 unlink "/var/lib/lxc/$vmid/config";
1448 rmdir "/var/lib/lxc/$vmid";
1449 destroy_config
($vmid);
1451 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1452 #PVE::Tools::run_command($cmd);
1455 sub vm_stop_cleanup
{
1456 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1461 my $vollist = get_vm_volumes
($conf);
1462 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1465 warn $@ if $@; # avoid errors - just warn
1468 my $safe_num_ne = sub {
1471 return 0 if !defined($a) && !defined($b);
1472 return 1 if !defined($a);
1473 return 1 if !defined($b);
1478 my $safe_string_ne = sub {
1481 return 0 if !defined($a) && !defined($b);
1482 return 1 if !defined($a);
1483 return 1 if !defined($b);
1489 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1491 if ($newnet->{type
} ne 'veth') {
1492 # for when there are physical interfaces
1493 die "cannot update interface of type $newnet->{type}";
1496 my $veth = "veth${vmid}i${netid}";
1497 my $eth = $newnet->{name
};
1499 if (my $oldnetcfg = $conf->{$opt}) {
1500 my $oldnet = parse_lxc_network
($oldnetcfg);
1502 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1503 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1505 PVE
::Network
::veth_delete
($veth);
1506 delete $conf->{$opt};
1507 write_config
($vmid, $conf);
1509 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1511 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1512 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1513 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1515 if ($oldnet->{bridge
}) {
1516 PVE
::Network
::tap_unplug
($veth);
1517 foreach (qw(bridge tag firewall)) {
1518 delete $oldnet->{$_};
1520 $conf->{$opt} = print_lxc_network
($oldnet);
1521 write_config
($vmid, $conf);
1524 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1525 foreach (qw(bridge tag firewall)) {
1526 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1528 $conf->{$opt} = print_lxc_network
($oldnet);
1529 write_config
($vmid, $conf);
1532 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1535 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1539 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1541 my $veth = "veth${vmid}i${netid}";
1542 my $vethpeer = $veth . "p";
1543 my $eth = $newnet->{name
};
1545 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1546 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1548 # attach peer in container
1549 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1550 PVE
::Tools
::run_command
($cmd);
1552 # link up peer in container
1553 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1554 PVE
::Tools
::run_command
($cmd);
1556 my $done = { type
=> 'veth' };
1557 foreach (qw(bridge tag firewall hwaddr name)) {
1558 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1560 $conf->{$opt} = print_lxc_network
($done);
1562 write_config
($vmid, $conf);
1565 sub update_ipconfig
{
1566 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1568 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1570 my $optdata = parse_lxc_network
($conf->{$opt});
1574 my $cmdargs = shift;
1575 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1577 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1579 my $change_ip_config = sub {
1580 my ($ipversion) = @_;
1582 my $family_opt = "-$ipversion";
1583 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1584 my $gw= "gw$suffix";
1585 my $ip= "ip$suffix";
1587 my $newip = $newnet->{$ip};
1588 my $newgw = $newnet->{$gw};
1589 my $oldip = $optdata->{$ip};
1591 my $change_ip = &$safe_string_ne($oldip, $newip);
1592 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1594 return if !$change_ip && !$change_gw;
1596 # step 1: add new IP, if this fails we cancel
1597 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1598 if ($change_ip && $is_real_ip) {
1599 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1606 # step 2: replace gateway
1607 # If this fails we delete the added IP and cancel.
1608 # If it succeeds we save the config and delete the old IP, ignoring
1609 # errors. The config is then saved.
1610 # Note: 'ip route replace' can add
1614 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1615 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1617 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1621 # the route was not replaced, the old IP is still available
1622 # rollback (delete new IP) and cancel
1624 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1625 warn $@ if $@; # no need to die here
1630 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1631 # if the route was not deleted, the guest might have deleted it manually
1637 # from this point on we save the configuration
1638 # step 3: delete old IP ignoring errors
1639 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1640 # We need to enable promote_secondaries, otherwise our newly added
1641 # address will be removed along with the old one.
1644 if ($ipversion == 4) {
1645 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1646 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1647 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1649 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1651 warn $@ if $@; # no need to die here
1653 if ($ipversion == 4) {
1654 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1658 foreach my $property ($ip, $gw) {
1659 if ($newnet->{$property}) {
1660 $optdata->{$property} = $newnet->{$property};
1662 delete $optdata->{$property};
1665 $conf->{$opt} = print_lxc_network
($optdata);
1666 write_config
($vmid, $conf);
1667 $lxc_setup->setup_network($conf);
1670 &$change_ip_config(4);
1671 &$change_ip_config(6);
1675 # Internal snapshots
1677 # NOTE: Snapshot create/delete involves several non-atomic
1678 # actions, and can take a long time.
1679 # So we try to avoid locking the file and use the 'lock' variable
1680 # inside the config file instead.
1682 my $snapshot_copy_config = sub {
1683 my ($source, $dest) = @_;
1685 foreach my $k (keys %$source) {
1686 next if $k eq 'snapshots';
1687 next if $k eq 'snapstate';
1688 next if $k eq 'snaptime';
1689 next if $k eq 'vmstate';
1690 next if $k eq 'lock';
1691 next if $k eq 'digest';
1692 next if $k eq 'description';
1694 $dest->{$k} = $source->{$k};
1698 my $snapshot_prepare = sub {
1699 my ($vmid, $snapname, $comment) = @_;
1703 my $updatefn = sub {
1705 my $conf = load_config
($vmid);
1707 die "you can't take a snapshot if it's a template\n"
1708 if is_template
($conf);
1712 $conf->{lock} = 'snapshot';
1714 die "snapshot name '$snapname' already used\n"
1715 if defined($conf->{snapshots
}->{$snapname});
1717 my $storecfg = PVE
::Storage
::config
();
1718 my $feature = $snapname eq 'vzdump' ?
'vzdump' : 'snapshot';
1719 die "snapshot feature is not available\n" if !has_feature
($feature, $conf, $storecfg);
1721 $snap = $conf->{snapshots
}->{$snapname} = {};
1723 &$snapshot_copy_config($conf, $snap);
1725 $snap->{'snapstate'} = "prepare";
1726 $snap->{'snaptime'} = time();
1727 $snap->{'description'} = $comment if $comment;
1728 $conf->{snapshots
}->{$snapname} = $snap;
1730 write_config
($vmid, $conf);
1733 lock_container
($vmid, 10, $updatefn);
1738 my $snapshot_commit = sub {
1739 my ($vmid, $snapname) = @_;
1741 my $updatefn = sub {
1743 my $conf = load_config
($vmid);
1745 die "missing snapshot lock\n"
1746 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1748 die "snapshot '$snapname' does not exist\n"
1749 if !defined($conf->{snapshots
}->{$snapname});
1751 die "wrong snapshot state\n"
1752 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1753 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1755 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1756 delete $conf->{lock};
1757 $conf->{parent
} = $snapname;
1759 write_config
($vmid, $conf);
1762 lock_container
($vmid, 10 ,$updatefn);
1766 my ($feature, $conf, $storecfg, $snapname) = @_;
1769 my $vzdump = $feature eq 'vzdump';
1770 $feature = 'snapshot' if $vzdump;
1772 foreach_mountpoint
($conf, sub {
1773 my ($ms, $mountpoint) = @_;
1775 return if $err; # skip further test
1776 return if $vzdump && $ms ne 'rootfs' && !$mountpoint->{backup
};
1778 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1780 # TODO: implement support for mountpoints
1781 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1785 return $err ?
0 : 1;
1788 sub snapshot_create
{
1789 my ($vmid, $snapname, $comment) = @_;
1791 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1793 my $conf = load_config
($vmid);
1795 my $running = check_running
($vmid);
1804 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1805 PVE
::Tools
::run_command
(['/bin/sync']);
1808 my $storecfg = PVE
::Storage
::config
();
1809 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1810 my $volid = $rootinfo->{volume
};
1812 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1813 $drivehash->{rootfs
} = 1;
1818 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
1823 eval { snapshot_delete
($vmid, $snapname, 1, $drivehash); };
1828 &$snapshot_commit($vmid, $snapname);
1831 # Note: $drivehash is only set when called from snapshot_create.
1832 sub snapshot_delete
{
1833 my ($vmid, $snapname, $force, $drivehash) = @_;
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};
1852 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1854 $snap->{snapstate
} = 'delete';
1856 write_config
($vmid, $conf);
1859 lock_container
($vmid, 10, $updatefn);
1861 my $storecfg = PVE
::Storage
::config
();
1863 my $unlink_parent = sub {
1865 my ($confref, $new_parent) = @_;
1867 if ($confref->{parent
} && $confref->{parent
} eq $snapname) {
1869 $confref->{parent
} = $new_parent;
1871 delete $confref->{parent
};
1876 my $del_snap = sub {
1878 $conf = load_config
($vmid);
1881 delete $conf->{lock};
1886 my $parent = $conf->{snapshots
}->{$snapname}->{parent
};
1887 foreach my $snapkey (keys %{$conf->{snapshots
}}) {
1888 &$unlink_parent($conf->{snapshots
}->{$snapkey}, $parent);
1891 &$unlink_parent($conf, $parent);
1893 delete $conf->{snapshots
}->{$snapname};
1895 write_config
($vmid, $conf);
1898 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1899 my $rootinfo = parse_ct_rootfs
($rootfs);
1900 my $volid = $rootinfo->{volume
};
1903 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1907 if(!$err || ($err && $force)) {
1908 lock_container
($vmid, 10, $del_snap);
1910 die "Can't delete snapshot: $vmid $snapname $err\n";
1915 sub snapshot_rollback
{
1916 my ($vmid, $snapname) = @_;
1918 my $storecfg = PVE
::Storage
::config
();
1920 my $conf = load_config
($vmid);
1922 die "you can't rollback if vm is a template\n" if is_template
($conf);
1924 my $snap = $conf->{snapshots
}->{$snapname};
1926 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1928 my $rootfs = $snap->{rootfs
};
1929 my $rootinfo = parse_ct_rootfs
($rootfs);
1930 my $volid = $rootinfo->{volume
};
1932 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1934 my $updatefn = sub {
1936 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1937 if $snap->{snapstate
};
1941 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1943 die "unable to rollback vm $vmid: vm is running\n"
1944 if check_running
($vmid);
1946 $conf->{lock} = 'rollback';
1950 # copy snapshot config to current config
1952 my $tmp_conf = $conf;
1953 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1954 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1955 delete $conf->{snaptime
};
1956 delete $conf->{snapname
};
1957 $conf->{parent
} = $snapname;
1959 write_config
($vmid, $conf);
1962 my $unlockfn = sub {
1963 delete $conf->{lock};
1964 write_config
($vmid, $conf);
1967 lock_container
($vmid, 10, $updatefn);
1969 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1971 lock_container
($vmid, 5, $unlockfn);
1974 sub template_create
{
1975 my ($vmid, $conf) = @_;
1977 my $storecfg = PVE
::Storage
::config
();
1979 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1980 my $volid = $rootinfo->{volume
};
1982 die "Template feature is not available for '$volid'\n"
1983 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1985 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1987 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1988 $rootinfo->{volume
} = $template_volid;
1989 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1991 write_config
($vmid, $conf);
1997 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
2000 sub mountpoint_names
{
2003 my @names = ('rootfs');
2005 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
2006 push @names, "mp$i";
2009 return $reverse ?
reverse @names : @names;
2013 sub foreach_mountpoint_full
{
2014 my ($conf, $reverse, $func) = @_;
2016 foreach my $key (mountpoint_names
($reverse)) {
2017 my $value = $conf->{$key};
2018 next if !defined($value);
2019 my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs
($value, 1) : parse_ct_mountpoint
($value, 1);
2020 next if !defined($mountpoint);
2022 &$func($key, $mountpoint);
2026 sub foreach_mountpoint
{
2027 my ($conf, $func) = @_;
2029 foreach_mountpoint_full
($conf, 0, $func);
2032 sub foreach_mountpoint_reverse
{
2033 my ($conf, $func) = @_;
2035 foreach_mountpoint_full
($conf, 1, $func);
2038 sub check_ct_modify_config_perm
{
2039 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2041 return 1 if $authuser ne 'root@pam';
2043 foreach my $opt (@$key_list) {
2045 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2046 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2047 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2048 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2049 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2050 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2051 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2052 $opt eq 'searchdomain' || $opt eq 'hostname') {
2053 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2055 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2063 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2065 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2066 my $volid_list = get_vm_volumes
($conf);
2068 foreach_mountpoint_reverse
($conf, sub {
2069 my ($ms, $mountpoint) = @_;
2071 my $volid = $mountpoint->{volume
};
2072 my $mount = $mountpoint->{mp
};
2074 return if !$volid || !$mount;
2076 my $mount_path = "$rootdir/$mount";
2077 $mount_path =~ s!/+!/!g;
2079 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2082 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2095 my ($vmid, $storage_cfg, $conf) = @_;
2097 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2098 File
::Path
::make_path
($rootdir);
2100 my $volid_list = get_vm_volumes
($conf);
2101 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2104 foreach_mountpoint
($conf, sub {
2105 my ($ms, $mountpoint) = @_;
2107 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2111 warn "mounting container failed\n";
2112 umount_all
($vmid, $storage_cfg, $conf, 1);
2120 sub mountpoint_mount_path
{
2121 my ($mountpoint, $storage_cfg, $snapname) = @_;
2123 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2126 my $check_mount_path = sub {
2128 $path = File
::Spec-
>canonpath($path);
2129 my $real = Cwd
::realpath
($path);
2130 if ($real ne $path) {
2131 die "mount path modified by symlink: $path != $real";
2140 if ($line =~ m
@^(/dev/loop\d
+):@) {
2144 my $cmd = ['losetup', '--associated', $path];
2145 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2149 # use $rootdir = undef to just return the corresponding mount path
2150 sub mountpoint_mount
{
2151 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2153 my $volid = $mountpoint->{volume
};
2154 my $mount = $mountpoint->{mp
};
2155 my $type = $mountpoint->{type
};
2157 return if !$volid || !$mount;
2161 if (defined($rootdir)) {
2162 $rootdir =~ s!/+$!!;
2163 $mount_path = "$rootdir/$mount";
2164 $mount_path =~ s!/+!/!g;
2165 &$check_mount_path($mount_path);
2166 File
::Path
::mkpath
($mount_path);
2169 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2171 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2174 if (defined($mountpoint->{acl
})) {
2175 $optstring .= ($mountpoint->{acl
} ?
'acl' : 'noacl');
2177 if ($mountpoint->{ro
}) {
2178 $optstring .= ',' if $optstring;
2182 my @extra_opts = ('-o', $optstring);
2186 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2187 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2189 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2190 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2192 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2194 if ($format eq 'subvol') {
2197 if ($scfg->{type
} eq 'zfspool') {
2198 my $path_arg = $path;
2199 $path_arg =~ s!^/+!!;
2200 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]);
2202 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2205 if ($mountpoint->{ro
}) {
2206 die "read-only bind mounts not supported\n";
2208 PVE
::Tools
::run_command
(['mount', '-o', 'bind', @extra_opts, $path, $mount_path]);
2211 return wantarray ?
($path, 0) : $path;
2212 } elsif ($format eq 'raw' || $format eq 'iso') {
2213 my $use_loopdev = 0;
2214 if ($scfg->{path
}) {
2215 push @extra_opts, '-o', 'loop';
2217 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' ||
2218 $scfg->{type
} eq 'rbd' || $scfg->{type
} eq 'lvmthin') {
2221 die "unsupported storage type '$scfg->{type}'\n";
2224 if ($format eq 'iso') {
2225 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2226 } elsif ($isBase || defined($snapname)) {
2227 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2229 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2232 return wantarray ?
($path, $use_loopdev) : $path;
2234 die "unsupported image format '$format'\n";
2236 } elsif ($type eq 'device') {
2237 PVE
::Tools
::run_command
(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
2238 return wantarray ?
($volid, 0) : $volid;
2239 } elsif ($type eq 'bind') {
2240 if ($mountpoint->{ro
}) {
2241 die "read-only bind mounts not supported\n";
2242 # Theoretically we'd have to execute both:
2243 # mount -o bind $a $b
2244 # mount -o bind,remount,ro $a $b
2246 die "directory '$volid' does not exist\n" if ! -d
$volid;
2247 &$check_mount_path($volid);
2248 PVE
::Tools
::run_command
(['mount', '-o', 'bind', @extra_opts, $volid, $mount_path]) if $mount_path;
2249 return wantarray ?
($volid, 0) : $volid;
2252 die "unsupported storage";
2255 sub get_vm_volumes
{
2256 my ($conf, $excludes) = @_;
2260 foreach_mountpoint
($conf, sub {
2261 my ($ms, $mountpoint) = @_;
2263 return if $excludes && $ms eq $excludes;
2265 my $volid = $mountpoint->{volume
};
2267 return if !$volid || $mountpoint->{type
} ne 'volume';
2269 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2272 push @$vollist, $volid;
2279 my ($dev, $rootuid, $rootgid) = @_;
2281 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2282 '-E', "root_owner=$rootuid:$rootgid",
2287 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2289 if ($volid =~ m!^/dev/.+!) {
2294 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2296 die "cannot format volume '$volid' with no storage\n" if !$storage;
2298 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2300 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2302 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2303 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2305 die "cannot format volume '$volid' (format == $format)\n"
2306 if $format ne 'raw';
2308 mkfs
($path, $rootuid, $rootgid);
2312 my ($storecfg, $vollist) = @_;
2314 foreach my $volid (@$vollist) {
2315 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2321 my ($storecfg, $vmid, $settings, $conf) = @_;
2326 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2327 my $chown_vollist = [];
2329 foreach_mountpoint
($settings, sub {
2330 my ($ms, $mountpoint) = @_;
2332 my $volid = $mountpoint->{volume
};
2333 my $mp = $mountpoint->{mp
};
2335 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2337 if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
2338 my ($storeid, $size_gb) = ($1, $2);
2340 my $size_kb = int(${size_gb
}*1024) * 1024;
2342 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2343 # fixme: use better naming ct-$vmid-disk-X.raw?
2345 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2347 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2349 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2351 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2353 push @$chown_vollist, $volid;
2355 } elsif ($scfg->{type
} eq 'zfspool') {
2357 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2359 push @$chown_vollist, $volid;
2360 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'lvmthin') {
2362 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2363 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2365 } elsif ($scfg->{type
} eq 'rbd') {
2367 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2368 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2369 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2371 die "unable to create containers on storage type '$scfg->{type}'\n";
2373 push @$vollist, $volid;
2374 $mountpoint->{volume
} = $volid;
2375 $mountpoint->{size
} = $size_kb * 1024;
2376 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2378 # use specified/existing volid/dir/device
2379 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2383 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2384 foreach my $volid (@$chown_vollist) {
2385 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2386 chown($rootuid, $rootgid, $path);
2388 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2390 # free allocated images on error
2392 destroy_disks
($storecfg, $vollist);
2398 # bash completion helper
2400 sub complete_os_templates
{
2401 my ($cmdname, $pname, $cvalue) = @_;
2403 my $cfg = PVE
::Storage
::config
();
2407 if ($cvalue =~ m/^([^:]+):/) {
2411 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2412 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2415 foreach my $id (keys %$data) {
2416 foreach my $item (@{$data->{$id}}) {
2417 push @$res, $item->{volid
} if defined($item->{volid
});
2424 my $complete_ctid_full = sub {
2427 my $idlist = vmstatus
();
2429 my $active_hash = list_active_containers
();
2433 foreach my $id (keys %$idlist) {
2434 my $d = $idlist->{$id};
2435 if (defined($running)) {
2436 next if $d->{template
};
2437 next if $running && !$active_hash->{$id};
2438 next if !$running && $active_hash->{$id};
2447 return &$complete_ctid_full();
2450 sub complete_ctid_stopped
{
2451 return &$complete_ctid_full(0);
2454 sub complete_ctid_running
{
2455 return &$complete_ctid_full(1);
2465 my $lxc = $conf->{lxc
};
2466 foreach my $entry (@$lxc) {
2467 my ($key, $value) = @$entry;
2468 next if $key ne 'lxc.id_map';
2469 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2470 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2471 push @$id_map, [$type, $ct, $host, $length];
2473 $rootuid = $host if $type eq 'u';
2474 $rootgid = $host if $type eq 'g';
2477 die "failed to parse id_map: $value\n";
2481 if (!@$id_map && $conf->{unprivileged
}) {
2482 # Should we read them from /etc/subuid?
2483 $id_map = [ ['u', '0', '100000', '65536'],
2484 ['g', '0', '100000', '65536'] ];
2485 $rootuid = $rootgid = 100000;
2488 return ($id_map, $rootuid, $rootgid);
2491 sub userns_command
{
2494 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];