]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
specify data format for hostname, searchdomain and nameserver
[pve-container.git] / src / PVE / LXC.pm
index ebf5d23bda73247c144dbf0462b54e30c4050497..3e1e5aa8c543abcda3331b418bc748e57c9cf7c3 100644 (file)
@@ -5,6 +5,8 @@ use warnings;
 use POSIX qw(EINTR);
 
 use File::Path;
+use File::Spec;
+use Cwd qw();
 use Fcntl ':flock';
 
 use PVE::Cluster qw(cfs_register_file cfs_read_file);
@@ -138,7 +140,7 @@ my $confdesc = {
     hostname => {
        optional => 1,
        description => "Set a host name for the container.",
-       type => 'string',
+       type => 'string', format => 'dns-name',
        maxLength => 255,
     },
     description => {
@@ -148,12 +150,12 @@ my $confdesc = {
     },
     searchdomain => {
        optional => 1,
-       type => 'string',
+       type => 'string', format => 'dns-name-list',
        description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
     },
     nameserver => {
        optional => 1,
-       type => 'string',
+       type => 'string', format => 'address-list',
        description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
     },
     rootfs => get_standard_option('pve-ct-rootfs'),
@@ -290,7 +292,9 @@ sub write_pct_config {
        foreach my $key (sort keys %$conf) {
            next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' || 
                $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
-           $raw .= "$key: $conf->{$key}\n";
+           my $value = $conf->{$key};
+           die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
+           $raw .= "$key: $value\n";
        }
 
        if (my $lxcconf = $conf->{lxc}) {
@@ -385,7 +389,7 @@ sub parse_pct_config {
            next;
        }
 
-       if ($line =~ m/^(lxc\.[a-z0-9_\.]+)(:|\s*=)\s*(.*?)\s*$/) {
+       if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
            my $key = $1;
            my $value = $3;
            if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
@@ -755,6 +759,23 @@ my $parse_size = sub {
     return int($size);
 };
 
+my $format_size = sub {
+    my ($size) = @_;
+
+    $size = int($size);
+
+    my $kb = int($size/1024);
+    return $size if $kb*1024 != $size;
+
+    my $mb = int($kb/1024);
+    return "${kb}K" if $mb*1024 != $kb;
+
+    my $gb = int($mb/1024);
+    return "${mb}M" if $gb*1024 != $mb;
+
+    return "${gb}G";
+};
+
 sub parse_ct_mountpoint {
     my ($data) = @_;
 
@@ -796,9 +817,14 @@ sub print_ct_mountpoint {
 
     die "missing volume\n" if !$info->{volume};
 
-    foreach my $o ('size', 'backup') {
+    foreach my $o (qw(backup)) {
        $opts .= ",$o=$info->{$o}" if defined($info->{$o});
     }
+
+    if ($info->{size}) {
+       $opts .= ",size=" . &$format_size($info->{size});
+    }
+
     $opts .= ",mp=$info->{mp}" if !$nomp;
 
     return "$info->{volume}$opts";
@@ -1010,15 +1036,9 @@ sub update_lxc_config {
 
     my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
     $mountpoint->{mp} = '/';
-    my $volid = $mountpoint->{volume};
-    my $path = mountpoint_mount_path($mountpoint, $storage_cfg);
     
-    my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
-
-    if ($storage) {
-       my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
-       $path = "loop:$path" if $scfg->{path};
-    }
+    my ($path, $use_loopdev) = mountpoint_mount_path($mountpoint, $storage_cfg);
+    $path = "loop:$path" if $use_loopdev;
 
     $raw .= "lxc.rootfs = $path\n";
 
@@ -1080,7 +1100,7 @@ sub update_pct_config {
 
     my @nohotplug;
 
-    my $new_disks = [];
+    my $new_disks = 0;
 
     my $rootdir;
     if ($running) {
@@ -1185,7 +1205,7 @@ sub update_pct_config {
            $conf->{$opt} = $value ? 1 : 0;
         } elsif ($opt =~ m/^mp(\d+)$/) {
            $conf->{$opt} = $value;
-           push @$new_disks, $opt;
+           $new_disks = 1;
            push @nohotplug, $opt;
            next;
         } elsif ($opt eq 'rootfs') {
@@ -1200,11 +1220,9 @@ sub update_pct_config {
        die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
     }
 
-    if (@$new_disks) {
+    if ($new_disks) {
        my $storage_cfg = PVE::Storage::config();
        create_disks($storage_cfg, $vmid, $conf, $conf);
-       mount_all($vmid, $storage_cfg, $conf, $new_disks, 1);
-       umount_all($vmid, $storage_cfg, $conf, 0);
     }
 }
 
@@ -1804,6 +1822,19 @@ sub mountpoint_names {
     return $reverse ? reverse @names : @names;
 }
 
+# The container might have *different* symlinks than the host. realpath/abs_path
+# use the actual filesystem to resolve links.
+sub sanitize_mountpoint {
+    my ($mp) = @_;
+    $mp = '/' . $mp; # we always start with a slash
+    $mp =~ s@/{2,}@/@g; # collapse sequences of slashes
+    $mp =~ s@/\./@@g; # collapse /./
+    $mp =~ s@/\.(/)?$@$1@; # collapse a trailing /. or /./
+    $mp =~ s@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
+    $mp =~ s@/\.\.(/)?$@$1@; # collapse trailing /.. or /../ disregarding symlinks
+    return $mp;
+}
+
 sub foreach_mountpoint_full {
     my ($conf, $reverse, $func) = @_;
 
@@ -1811,7 +1842,14 @@ sub foreach_mountpoint_full {
        my $value = $conf->{$key};
        next if !defined($value);
        my $mountpoint = parse_ct_mountpoint($value);
-       $mountpoint->{mp} = '/' if $key eq 'rootfs'; # just to be sure
+
+       # just to be sure: rootfs is /
+       my $path = $key eq 'rootfs' ? '/' : $mountpoint->{mp};
+       $mountpoint->{mp} = sanitize_mountpoint($path);
+
+       $path = $mountpoint->{volume};
+       $mountpoint->{volume} = sanitize_mountpoint($path) if $path =~ m|^/|;
+
        &$func($key, $mountpoint);
     }
 }
@@ -1837,7 +1875,7 @@ sub check_ct_modify_config_perm {
 
        if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
-       } elsif ($opt eq 'disk') {
+       } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
        } elsif ($opt eq 'memory' || $opt eq 'swap') {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
@@ -1885,7 +1923,7 @@ sub umount_all {
 }
 
 sub mount_all {
-    my ($vmid, $storage_cfg, $conf, $mkdirs) = @_;
+    my ($vmid, $storage_cfg, $conf) = @_;
 
     my $rootdir = "/var/lib/lxc/$vmid/rootfs";
     File::Path::make_path($rootdir);
@@ -1908,7 +1946,6 @@ sub mount_all {
 
            die "unable to mount base volume - internal error" if $isBase;
 
-           File::Path::make_path "$rootdir/$mount" if $mkdirs;
            mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
         });
     };
@@ -1927,6 +1964,15 @@ sub mountpoint_mount_path {
     return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
 }
 
+my $check_mount_path = sub {
+    my ($path) = @_;
+    $path = File::Spec->canonpath($path);
+    my $real = Cwd::realpath($path);
+    if ($real ne $path) {
+       die "mount path modified by symlink: $path != $real";
+    }
+};
+
 # use $rootdir = undef to just return the corresponding mount path
 sub mountpoint_mount {
     my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
@@ -1942,6 +1988,7 @@ sub mountpoint_mount {
        $rootdir =~ s!/+$!!;
        $mount_path = "$rootdir/$mount";
        $mount_path =~ s!/+!/!g;
+       &$check_mount_path($mount_path);
        File::Path::mkpath($mount_path);
     }
     
@@ -1953,48 +2000,54 @@ sub mountpoint_mount {
 
        my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
        my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
-       return $path if !$mount_path;
 
        my ($vtype, undef, undef, undef, undef, $isBase, $format) =
            PVE::Storage::parse_volname($storage_cfg, $volid);
 
        if ($format eq 'subvol') {
-           if ($snapname) {
-               if ($scfg->{type} eq 'zfspool') {
-                   my $path_arg = $path;
-                   $path_arg =~ s!^/+!!;
-                   PVE::Tools::run_command(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
+           if ($mount_path) {
+               if ($snapname) {
+                   if ($scfg->{type} eq 'zfspool') {
+                       my $path_arg = $path;
+                       $path_arg =~ s!^/+!!;
+                       PVE::Tools::run_command(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
+                   } else {
+                       die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
+                   }
                } else {
-                   die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
-               }               
-           } else {
-               PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
+                   PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
+               }
            }
-           return $path;
+           return wantarray ? ($path, 0) : $path;
        } elsif ($format eq 'raw') {
+           my $use_loopdev = 0;
            my @extra_opts;
            if ($scfg->{path}) {
                push @extra_opts, '-o', 'loop';
-           } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'rbd') {
+               $use_loopdev = 1;
+           } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'rbd') {
                # do nothing
            } else {
                die "unsupported storage type '$scfg->{type}'\n";
            }
-           if ($isBase || defined($snapname)) {
-               PVE::Tools::run_command(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
-           } else {
-               PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
+           if ($mount_path) {
+               if ($isBase || defined($snapname)) {
+                   PVE::Tools::run_command(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
+               } else {
+                   PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
+               }
            }
-           return $path;
+           return wantarray ? ($path, $use_loopdev) : $path;
        } else {
            die "unsupported image format '$format'\n";
        }
     } elsif ($volid =~ m|^/dev/.+|) {
        PVE::Tools::run_command(['mount', $volid, $mount_path]) if $mount_path;
-       return $volid;
+       return wantarray ? ($volid, 0) : $volid;
     } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
+       &$check_mount_path($volid);
        PVE::Tools::run_command(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
-       return $volid;
+       return wantarray ? ($volid, 0) : $volid;
     }
     
     die "unsupported storage";
@@ -2041,6 +2094,8 @@ sub format_disk {
 
     die "cannot format volume '$volid' with no storage\n" if !$storage;
 
+    PVE::Storage::activate_volumes($storage_cfg, [$volid]);
+
     my $path = PVE::Storage::path($storage_cfg, $volid);
 
     my ($vtype, undef, undef, undef, undef, $isBase, $format) =
@@ -2078,17 +2133,17 @@ sub create_disks {
            return if !$storage;
 
            if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
-               my ($storeid, $size) = ($1, $2);
+               my ($storeid, $size_gb) = ($1, $2);
 
-               $size = int($size*1024) * 1024;
+               my $size_kb = int(${size_gb}*1024) * 1024;
 
                my $scfg = PVE::Storage::storage_config($storecfg, $storage);
                # fixme: use better naming ct-$vmid-disk-X.raw?
 
                if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
-                   if ($size > 0) {
+                   if ($size_kb > 0) {
                        $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
-                                                          undef, $size);
+                                                          undef, $size_kb);
                        format_disk($storecfg, $volid);
                    } else {
                        $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
@@ -2097,22 +2152,22 @@ sub create_disks {
                } elsif ($scfg->{type} eq 'zfspool') {
 
                    $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
-                                              undef, $size);
-               } elsif ($scfg->{type} eq 'drbd') {
+                                                      undef, $size_kb);
+               } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm') {
 
-                   $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size);
+                   $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
                    format_disk($storecfg, $volid);
 
                } elsif ($scfg->{type} eq 'rbd') {
 
                    die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
-                   $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size);
+                   $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
                    format_disk($storecfg, $volid);
                } else {
                    die "unable to create containers on storage type '$scfg->{type}'\n";
                }
                push @$vollist, $volid;
-                my $new_mountpoint = { volume => $volid, size => $size, mp => $mp };
+                my $new_mountpoint = { volume => $volid, size => $size_kb*1024, mp => $mp };
                $conf->{$ms} = print_ct_mountpoint($new_mountpoint, $ms eq 'rootfs');
            } else {
                # use specified/existing volid