]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC/Config.pm
migrate: also set targetsid for unreferenced disks
[pve-container.git] / src / PVE / LXC / Config.pm
index 5bf12d56286fa337fde36ef7cabf87b41ee53f7f..0ed7bd245dd9db32745eb2d6460ffba030807ab9 100644 (file)
@@ -2,6 +2,7 @@ package PVE::LXC::Config;
 
 use strict;
 use warnings;
+use Fcntl qw(O_RDONLY);
 
 use PVE::AbstractConfig;
 use PVE::Cluster qw(cfs_register_file);
@@ -13,6 +14,9 @@ use PVE::Tools;
 
 use base qw(PVE::AbstractConfig);
 
+use constant {FIFREEZE => 0xc0045877,
+              FITHAW   => 0xc0045878};
+
 my $nodename = PVE::INotify::nodename();
 my $lock_handles =  {};
 my $lockdir = "/run/lock/lxc";
@@ -97,6 +101,25 @@ sub __snapshot_save_vmstate {
     die "implement me - snapshot_save_vmstate\n";
 }
 
+sub __snapshot_activate_storages {
+    my ($class, $conf, $include_vmstate) = @_;
+
+    my $storecfg = PVE::Storage::config();
+    my $opts = $include_vmstate ? { 'extra_keys' => ['vmstate'] } : {};
+    my $storage_hash = {};
+
+    $class->foreach_volume_full($conf, $opts, sub {
+       my ($vs, $mountpoint) = @_;
+
+       return if $mountpoint->{type} ne 'volume';
+
+       my ($storeid) = PVE::Storage::parse_volume_id($mountpoint->{volume});
+       $storage_hash->{$storeid} = 1;
+    });
+
+    PVE::Storage::activate_storage_list($storecfg, [ sort keys $storage_hash->%* ]);
+}
+
 sub __snapshot_check_running {
     my ($class, $vmid) = @_;
     return PVE::LXC::check_running($vmid);
@@ -109,15 +132,60 @@ sub __snapshot_check_freeze_needed {
     return ($ret, $ret);
 }
 
+# implements similar functionality to fsfreeze(8)
+sub fsfreeze_mountpoint {
+    my ($path, $thaw) = @_;
+
+    my $op = $thaw ? 'thaw' : 'freeze';
+    my $ioctl = $thaw ? FITHAW : FIFREEZE;
+
+    sysopen my $fd, $path, O_RDONLY or die "failed to open $path: $!\n";
+    my $ioctl_err;
+    if (!ioctl($fd, $ioctl, 0)) {
+       $ioctl_err = "$!";
+    }
+    close($fd);
+    die "fs$op '$path' failed - $ioctl_err\n" if defined $ioctl_err;
+}
+
 sub __snapshot_freeze {
     my ($class, $vmid, $unfreeze) = @_;
 
+    my $conf = $class->load_config($vmid);
+    my $storagecfg = PVE::Storage::config();
+
+    my $freeze_mps = [];
+    $class->foreach_volume($conf, sub {
+       my ($ms, $mountpoint) = @_;
+
+       return if $mountpoint->{type} ne 'volume';
+
+       if (PVE::Storage::volume_snapshot_needs_fsfreeze($storagecfg, $mountpoint->{volume})) {
+           push @$freeze_mps, $mountpoint->{mp};
+       }
+    });
+
+    my $freeze_mountpoints = sub {
+       my ($thaw) = @_;
+
+       return if scalar(@$freeze_mps) == 0;
+
+       my $pid = PVE::LXC::find_lxc_pid($vmid);
+
+       for my $mp (@$freeze_mps) {
+           eval{ fsfreeze_mountpoint("/proc/${pid}/root/${mp}", $thaw); };
+           warn $@ if $@;
+       }
+    };
+
     if ($unfreeze) {
        eval { PVE::LXC::thaw($vmid); };
        warn $@ if $@;
+       $freeze_mountpoints->(1);
     } else {
        PVE::LXC::freeze($vmid);
        PVE::LXC::sync_container_namespace($vmid);
+       $freeze_mountpoints->(0);
     }
 }
 
@@ -143,7 +211,7 @@ sub __snapshot_delete_remove_drive {
        delete $snap->{$remove_drive};
 
        $class->add_unused_volume($snap, $mountpoint->{volume})
-           if ($mountpoint->{type} eq 'volume');
+           if $mountpoint && ($mountpoint->{type} eq 'volume');
     }
 }
 
@@ -165,10 +233,15 @@ sub __snapshot_delete_vol_snapshot {
 }
 
 sub __snapshot_rollback_vol_possible {
-    my ($class, $mountpoint, $snapname) = @_;
+    my ($class, $mountpoint, $snapname, $blockers) = @_;
 
     my $storecfg = PVE::Storage::config();
-    PVE::Storage::volume_rollback_is_possible($storecfg, $mountpoint->{volume}, $snapname);
+    PVE::Storage::volume_rollback_is_possible(
+       $storecfg,
+       $mountpoint->{volume},
+       $snapname,
+       $blockers,
+    );
 }
 
 sub __snapshot_rollback_vol_rollback {
@@ -227,7 +300,7 @@ sub __snapshot_rollback_get_unused {
 cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
 
 
-my $valid_mount_option_re = qr/(noatime|nodev|nosuid|noexec)/;
+my $valid_mount_option_re = qr/(noatime|lazytime|nodev|nosuid|noexec)/;
 
 sub is_valid_mount_option {
     my ($option) = @_;
@@ -292,6 +365,23 @@ PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
     optional => 1,
 });
 
+# IP address with optional interface suffix for link local ipv6 addresses
+PVE::JSONSchema::register_format('lxc-ip-with-ll-iface', \&verify_ip_with_ll_iface);
+sub verify_ip_with_ll_iface {
+    my ($addr, $noerr) = @_;
+
+    if (my ($addr, $iface) = ($addr =~ /^(fe80:[^%]+)%(.*)$/)) {
+       if (PVE::JSONSchema::pve_verify_ip($addr, 1)
+           && PVE::JSONSchema::pve_verify_iface($iface, 1))
+       {
+           return $addr;
+       }
+    }
+
+    return PVE::JSONSchema::pve_verify_ip($addr, $noerr);
+}
+
+
 my $features_desc = {
     mount => {
        optional => 1,
@@ -379,7 +469,7 @@ my $confdesc = {
     ostype => {
        optional => 1,
        type => 'string',
-       enum => [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
+       enum => [qw(debian devuan ubuntu centos fedora opensuse archlinux alpine gentoo nixos unmanaged)],
        description => "OS type. This is used to setup configuration inside the container, and corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf. Value 'unmanaged' can be used to skip and OS specific setup.",
     },
     console => {
@@ -401,14 +491,14 @@ my $confdesc = {
        type => 'integer',
        description => "The number of cores assigned to the container. A container can use all available cores by default.",
        minimum => 1,
-       maximum => 128,
+       maximum => 8192,
     },
     cpulimit => {
        optional => 1,
        type => 'number',
        description => "Limit of CPU usage.\n\nNOTE: If the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.",
        minimum => 0,
-       maximum => 128,
+       maximum => 8192,
        default => 0,
     },
     cpuunits => {
@@ -442,7 +532,9 @@ my $confdesc = {
     description => {
        optional => 1,
        type => 'string',
-        description => "Container description. Only used on the configuration web interface.",
+       description => "Description for the Container. Shown in the web-interface CT's summary."
+           ." This is saved as comment inside the configuration file.",
+       maxLength => 1024 * 8,
     },
     searchdomain => {
        optional => 1,
@@ -451,7 +543,7 @@ my $confdesc = {
     },
     nameserver => {
        optional => 1,
-       type => 'string', format => 'address-list',
+       type => 'string', format => 'lxc-ip-with-ll-iface-list',
        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.",
     },
     timezone => {
@@ -508,6 +600,12 @@ my $confdesc = {
        description => 'Tags of the Container. This is only meta information.',
        optional => 1,
     },
+    debug => {
+       optional => 1,
+       type => 'boolean',
+       description => "Try to be more verbose. For now this only enables debug log-level on start.",
+       default => 0,
+    },
 };
 
 my $valid_lxc_conf_keys = {
@@ -712,7 +810,7 @@ our $netconf_desc = {
 };
 PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
 
-my $MAX_LXC_NETWORKS = 10;
+my $MAX_LXC_NETWORKS = 32;
 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
     $confdesc->{"net$i"} = {
        optional => 1,
@@ -783,7 +881,8 @@ for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
     $confdesc->{"mp$i"} = {
        optional => 1,
        type => 'string', format => $mp_desc,
-       description => "Use volume as container mount point.",
+       description => "Use volume as container mount point. Use the special " .
+           "syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.",
        optional => 1,
     };
 }
@@ -797,7 +896,7 @@ for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
 }
 
 sub parse_pct_config {
-    my ($filename, $raw) = @_;
+    my ($filename, $raw, $strict) = @_;
 
     return undef if !defined($raw);
 
@@ -807,6 +906,16 @@ sub parse_pct_config {
        pending => {},
     };
 
+    my $handle_error = sub {
+       my ($msg) = @_;
+
+       if ($strict) {
+           die $msg;
+       } else {
+           warn $msg;
+       }
+    };
+
     $filename =~ m|/lxc/(\d+).conf$|
        || die "got strange filename '$filename'";
 
@@ -846,9 +955,9 @@ sub parse_pct_config {
            if ($validity eq 1) {
                push @{$conf->{lxc}}, [$key, $value];
            } elsif (my $errmsg = $validity) {
-               warn "vm $vmid - $key: $errmsg\n";
+               $handle_error->("vm $vmid - $key: $errmsg\n");
            } else {
-               warn "vm $vmid - unable to parse config: $line\n";
+               $handle_error->("vm $vmid - unable to parse config: $line\n");
            }
        } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
            $descr .= PVE::Tools::decode_text($2);
@@ -859,16 +968,16 @@ sub parse_pct_config {
            if ($section eq 'pending') {
                $conf->{delete} = $value;
            } else {
-               warn "vm $vmid - property 'delete' is only allowed in [pve:pending]\n";
+               $handle_error->("vm $vmid - property 'delete' is only allowed in [pve:pending]\n");
            }
-       } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
+       } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) {
            my $key = $1;
            my $value = $2;
            eval { $value = PVE::LXC::Config->check_type($key, $value); };
-           warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
+           $handle_error->("vm $vmid - unable to parse value of '$key' - $@") if $@;
            $conf->{$key} = $value;
        } else {
-           warn "vm $vmid - unable to parse config: $line\n";
+           $handle_error->("vm $vmid - unable to parse config: $line\n");
        }
     }
 
@@ -1111,7 +1220,9 @@ sub parse_volume {
        return $parse_ct_mountpoint_full->($class, $unused_desc, $volume_string, $noerr);
     }
 
-    die "parse_volume - unknown type: $key\n";
+    die "parse_volume - unknown type: $key\n" if !$noerr;
+
+    return;
 }
 
 sub print_volume {
@@ -1220,7 +1331,7 @@ sub vmconfig_hotplug_pending {
            } elsif ($opt eq 'swap') {
                $hotplug_memory->(undef, 0);
            } elsif ($opt eq 'cpulimit') {
-               $cgroup->change_cpu_quota(-1, 100000);
+               $cgroup->change_cpu_quota(undef, undef); # reset, cgroup module can better decide values
            } elsif ($opt eq 'cpuunits') {
                $cgroup->change_cpu_shares(undef, $confdesc->{cpuunits}->{default});
            } elsif ($opt =~ m/^net(\d)$/) {
@@ -1481,6 +1592,15 @@ sub valid_volume_keys {
     return $reverse ? reverse @names : @names;
 }
 
+sub valid_volume_keys_with_unused {
+    my ($class, $reverse) = @_;
+    my @names = $class->valid_volume_keys();
+    for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
+       push @names, "unused$i";
+    }
+    return $reverse ? reverse @names : @names;
+}
+
 sub get_vm_volumes {
     my ($class, $conf, $excludes) = @_;