]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/VZDump/LXC.pm
VZDump lock update, drop lock_aquire/lock_release
[pve-container.git] / src / PVE / VZDump / LXC.pm
index ccad9cd561273a316ffb2d9ae46186b84fdd0252..57dcbe253610ed5f254cc68ad56db52c9eb94134 100644 (file)
@@ -16,20 +16,37 @@ use base qw (PVE::VZDump::Plugin);
 my $default_mount_point = "/mnt/vzsnap0";
 
 my $rsync_vm = sub {
-    my ($self, $task, $from, $to, $text) = @_;
+    my ($self, $task, $to, $text) = @_;
 
+    my $disks = $task->{disks};
+    my $from = $disks->[0]->{dir} . '/';
     $self->loginfo ("starting $text sync $from to $to");
 
-    my $starttime = time();
-
     my $opts = $self->{vzdump}->{opts};
 
-    my $rsyncopts = "--stats -x -X --numeric-ids";
+    my @xattr = $task->{no_xattrs} ? () : ('-X', '-A');
 
-    $rsyncopts .= " --bwlimit=$opts->{bwlimit}" if $opts->{bwlimit};
-
-    $self->cmd ("rsync $rsyncopts -aH --delete --no-whole-file --inplace '$from' '$to'");
+    my $rsync = ['rsync', '--stats', @xattr, '--numeric-ids',
+                 '-aH', '--delete', '--no-whole-file', '--inplace',
+                 '--one-file-system', '--relative'];
+    push @$rsync, "--bwlimit=$opts->{bwlimit}" if $opts->{bwlimit};
+    push @$rsync, map { "--exclude=$_" } @{$self->{vzdump}->{findexcl}};
+    push @$rsync, map { "--exclude=$_" } @{$task->{exclude_dirs}};
 
+    my $starttime = time();
+    # See the rsync(1) manpage for --relative in conjunction with /./ in paths.
+    # This is the only way to have exclude-dirs work together with the
+    # --one-file-system option.
+    # This way we can pass multiple source paths and tell rsync which directory
+    # they're supposed to be relative to.
+    # Otherwise with eg. using multiple rsync commands means the --exclude
+    # directives need to be modified for every command as they are meant to be
+    # relative to the rootdir, while rsync treats them as relative to the
+    # source dir.
+    foreach my $disk (@$disks) {
+       push @$rsync, "$from/.$disk->{mp}";
+    }
+    $self->cmd([@$rsync, $to]);
     my $delay = time () - $starttime;
 
     $self->loginfo ("$text sync finished ($delay seconds)");
@@ -81,79 +98,117 @@ sub prepare {
     my ($self, $task, $vmid, $mode) = @_;
 
     my $conf = $self->{vmlist}->{$vmid} = PVE::LXC::load_config($vmid);
-
-    PVE::LXC::foreach_mountpoint($conf, sub {
-       my ($ms, $mountpoint) = @_;
-
-       return if $ms eq 'rootfs';
-       # TODO: implement support for mountpoints
-       die "unable to backup mountpoint '$ms' - feature not implemented\n";
-    });
+    my $storage_cfg = $self->{storecfg};
 
     my $running = PVE::LXC::check_running($vmid);
 
-    my $diskinfo = $task->{diskinfo} = {};
+    my $disks = $task->{disks} = [];
+    my $exclude_dirs = $task->{exclude_dirs} = [];
 
     $task->{hostname} = $conf->{'hostname'} || "CT$vmid";
 
-    my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
-    $diskinfo->{volid} = $rootinfo->{volume};
+    my ($id_map, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf);
+    $task->{userns_cmd} = PVE::LXC::userns_command($id_map);
 
-    die "missing root volid (no volid)\n" if !$diskinfo->{volid};
+    my $volid_list = [];
+    PVE::LXC::foreach_mountpoint($conf, sub {
+       my ($name, $data) = @_;
+       my $volid = $data->{volume};
+       my $mount = $data->{mp};
+       my $type = $data->{type};
 
-    # fixme: when do we deactivate ??
-    PVE::Storage::activate_volumes($self->{storecfg}, [$diskinfo->{volid}]);
+       return if !$volid || !$mount;
 
-    if ($mode eq 'snapshot') {
+       if ($name ne 'rootfs' && !$data->{backup}) {
+           push @$exclude_dirs, $mount;
+           return;
+       }
+
+       push @$disks, $data;
+       push @$volid_list, $volid
+           if $type eq 'volume';
+    });
 
-       if (!PVE::LXC::has_feature('snapshot', $conf, $self->{storecfg})) {
-           die "mode failure - some volumes does not support snapshots\n";
+    if ($mode eq 'snapshot') {
+       if (!PVE::LXC::has_feature('vzdump', $conf, $storage_cfg)) {
+           die "mode failure - some volumes do not support snapshots\n";
        }
 
+       unlock_vm($self, $vmid);
+
        if ($conf->{snapshots} && $conf->{snapshots}->{vzdump}) {
            $self->loginfo("found old vzdump snapshot (force removal)");
-           PVE::LXC::snapshot_delete($vmid, 'vzdump', 0);
+           PVE::LXC::snapshot_delete($vmid, 'vzdump', 1);
        }
 
        my $rootdir = $default_mount_point;
        mkpath $rootdir;
        &$check_mountpoint_empty($rootdir);
 
-       # set snapshot_count (freezes CT it snapshot_count > 1)
-       my $volid_list = PVE::LXC::get_vm_volumes($conf);
+       # set snapshot_count (freezes CT if snapshot_count > 1)
        $task->{snapshot_count} = scalar(@$volid_list);
-       
     } elsif ($mode eq 'stop') {
        my $rootdir = $default_mount_point;
        mkpath $rootdir;
        &$check_mountpoint_empty($rootdir);
+       PVE::Storage::activate_volumes($storage_cfg, $volid_list);
     } elsif ($mode eq 'suspend') {
        my $pid = PVE::LXC::find_lxc_pid($vmid);
-       $diskinfo->{dir} = "/proc/$pid/root";
+       foreach my $disk (@$disks) {
+           $disk->{dir} = "/proc/$pid/root$disk->{mp}";
+       }
        $task->{snapdir} = $task->{tmpdir};
     } else {
+       unlock_vm($self, $vmid);
        die "unknown mode '$mode'\n"; # should not happen
     }
+
+    if ($mode ne 'suspend') {
+       # If we perform mount operations, let's unshare the mount namespace
+       # to not influence the running host.
+       PVE::Tools::unshare(PVE::Tools::CLONE_NEWNS);
+       PVE::Tools::run_command(['mount', '--make-rprivate', '/']);
+    }
 }
 
 sub lock_vm {
     my ($self, $vmid) = @_;
 
-    PVE::LXC::lock_aquire($vmid);
+    my $lockconfig = sub {
+       my ($self, $vmid) = @_;
+
+       my $conf = PVE::LXC::load_config($vmid);
+
+       PVE::LXC::check_lock($conf);
+       $conf->{lock} = 'backup';
+
+       PVE::LXC::write_config($vmid, $conf);
+    };
+
+    PVE::LXC::lock_container($vmid, 10, $lockconfig, ($self, $vmid));
 }
 
 sub unlock_vm {
     my ($self, $vmid) = @_;
 
-    PVE::LXC::lock_release($vmid);
+    my $unlockconfig = sub {
+       my ($self, $vmid) = @_;
+
+       my $conf = PVE::LXC::load_config($vmid);
+
+       if ($conf->{lock} && $conf->{lock} eq 'backup') {
+           delete $conf->{lock};
+           PVE::LXC::write_config($vmid, $conf);
+       }
+    };
+
+    PVE::LXC::lock_container($vmid, 60, $unlockconfig, ($self, $vmid));
 }
 
 sub snapshot {
     my ($self, $task, $vmid) = @_;
 
-    my $diskinfo = $task->{diskinfo};
-
-    $self->loginfo("create storage snapshot snapshot");
+    $self->loginfo("create storage snapshot 'vzdump'");
 
     # todo: freeze/unfreeze if we have more than one volid
     PVE::LXC::snapshot_create($vmid, 'vzdump', "vzdump backup snapshot");
@@ -161,38 +216,52 @@ sub snapshot {
     
     # reload config
     my $conf = $self->{vmlist}->{$vmid} = PVE::LXC::load_config($vmid);
-    die "unable to read vzdump shanpshot config - internal error"
+    die "unable to read vzdump snapshot config - internal error"
        if !($conf->{snapshots} && $conf->{snapshots}->{vzdump});
 
-    # my $snapconf = $conf->{snapshots}->{vzdump};
-    # my $volid_list = PVE::LXC::get_vm_volumes($snapconf);
-    my $volid_list = [$diskinfo->{volid}];
+    my $disks = $task->{disks};
+    #todo: reevaluate bind/dev mount handling when implementing snapshots for mps
+    my $volid_list = [map { $_->{volume} } @$disks];
 
     my $rootdir = $default_mount_point;
-       
-    my $mp = { volume => $diskinfo->{volid}, mp => "/" };
-    PVE::LXC::mountpoint_mount($mp, $rootdir, $self->{storecfg}, 'vzdump');
-    $diskinfo->{dir} = $diskinfo->{mountpoint} = $rootdir;
-    $task->{snapdir} = $diskinfo->{dir};
+    my $storage_cfg = $self->{storecfg};
+
+    PVE::Storage::activate_volumes($storage_cfg, $volid_list, 'vzdump');
+    foreach my $disk (@$disks) {
+       $disk->{dir} = "${rootdir}$disk->{mp}";
+       PVE::LXC::mountpoint_mount($disk, $rootdir, $storage_cfg, 'vzdump');
+    }
+
+    $task->{snapdir} = $rootdir;
 }
 
 sub copy_data_phase1 {
     my ($self, $task) = @_;
 
-    $self->$rsync_vm($task, "$task->{diskinfo}->{dir}/", $task->{snapdir}, "first");
+    if (my $mntinfo = PVE::VZDump::get_mount_info($task->{snapdir})) {
+       if ($mntinfo->{fstype} =~ /^nfs4?/) {
+           warn "temporary directory is on NFS, disabling xattr and acl support"
+           . ", consider configuring a local tmpdir via /etc/vzdump.conf\n";
+           $task->{no_xattrs} = 1;
+       }
+    }
+
+    $self->$rsync_vm($task, $task->{snapdir}, "first");
 }
 
 sub copy_data_phase2 {
     my ($self, $task) = @_;
 
-    $self->$rsync_vm ($task, "$task->{diskinfo}->{dir}/", $task->{snapdir}, "final");
+    $self->$rsync_vm($task, $task->{snapdir}, "final");
 }
 
 sub stop_vm {
     my ($self, $task, $vmid) = @_;
 
     $self->cmd("lxc-stop -n $vmid");
+
+    # make sure container is stopped
+    $self->cmd("lxc-wait -n $vmid -s STOPPED");
 }
 
 sub start_vm {
@@ -221,79 +290,96 @@ sub assemble {
     mkpath "$tmpdir/etc/vzdump/";
 
     my $conf = PVE::LXC::load_config($vmid);
+    delete $conf->{lock};
     delete $conf->{snapshots};
     delete $conf->{'pve.parent'};
 
     PVE::Tools::file_set_contents("$tmpdir/etc/vzdump/pct.conf", PVE::LXC::write_pct_config("/lxc/$vmid.conf", $conf));
+
+    my $firewall ="/etc/pve/firewall/$vmid.fw";
+    if (-e  $firewall) {
+       PVE::Tools::file_copy($firewall, "$tmpdir/etc/vzdump/pct.fw");
+       $task->{fw} = 1;
+    }
 }
 
 sub archive {
     my ($self, $task, $vmid, $filename, $comp) = @_;
 
+    my $disks = $task->{disks};
+    my @sources;
+
     if ($task->{mode} eq 'stop') {
        my $rootdir = $default_mount_point;
-       my $diskinfo = $task->{diskinfo};
-
-       my $volid_list = [$diskinfo->{volid}];
-       my $mp = { volume => $diskinfo->{volid}, mp => "/" };
-
-       $self->loginfo("mounting container root at '$rootdir'");
-       PVE::LXC::mountpoint_mount($mp, $rootdir, $self->{storecfg});
-
-       $diskinfo->{dir} = $diskinfo->{mountpoint} = $rootdir;
-       $task->{snapdir} = $diskinfo->{dir};
+       my $storage_cfg = $self->{storecfg};
+       foreach my $disk (@$disks) {
+           $disk->{dir} = "${rootdir}$disk->{mp}";
+           PVE::LXC::mountpoint_mount($disk, $rootdir, $storage_cfg);
+           # add every enabled mountpoint (since we use --one-file-system)
+           # mp already starts with a / so we only need to add the dot
+           push @sources, ".$disk->{mp}";
+       }
+       $task->{snapdir} = $rootdir;
+    } else {
+       # the data was rsynced to a temporary location, only use '.' to avoid
+       # having mountpoints duplicated
+       push @sources, '.';
     }
 
-    my $findexcl = $self->{vzdump}->{findexcl};
-    push @$findexcl, "'('", '-path', "./etc/vzdump", "-prune", "')'", '-o';
-
-    my $findargs = join (' ', @$findexcl) . ' -print0';
     my $opts = $self->{vzdump}->{opts};
-
-    my $srcdir = $task->{diskinfo}->{dir};
     my $snapdir = $task->{snapdir};
     my $tmpdir = $task->{tmpdir};
 
-    my $taropts = "--totals --sparse --numeric-owner --no-recursion --xattrs --one-file-system";
+    my $userns_cmd = $task->{userns_cmd};
+    my $tar = [@$userns_cmd, 'tar', 'cpf', '-', '--totals',
+               @$PVE::LXC::COMMON_TAR_FLAGS,
+               '--one-file-system', '--warning=no-file-ignored'];
 
     # note: --remove-files does not work because we do not 
     # backup all files (filters). tar complains:
     # Cannot rmdir: Directory not empty
-    # we we disable this optimization for now
+    # we disable this optimization for now
     #if ($snapdir eq $task->{tmpdir} && $snapdir =~ m|^$opts->{dumpdir}/|) {
-    #       $taropts .= " --remove-files"; # try to save space
+    #       push @$tar, "--remove-files"; # try to save space
     #}
 
-    my $cmd = "(";
-
-    $cmd .= "cd $snapdir;find . $findargs|sed 's/\\\\/\\\\\\\\/g'|";
-    $cmd .= "tar cpf - $taropts ";
-    # The directory parameter can give a alternative directory as source.
+    # The directory parameter can give an alternative directory as source.
     # the second parameter gives the structure in the tar.
-    $cmd .= "--directory=$tmpdir ./etc/vzdump/pct.conf ";
-    $cmd .= "--directory=$snapdir --null -T -";
+    push @$tar, "--directory=$tmpdir", './etc/vzdump/pct.conf';
+    push @$tar, "./etc/vzdump/pct.fw" if $task->{fw};
+    push @$tar, "--directory=$snapdir";
+    push @$tar, '--no-anchored', '--exclude=lost+found' if $userns_cmd;
+    push @$tar, '--anchored';
+    push @$tar, map { "--exclude=.$_" } @{$self->{vzdump}->{findexcl}};
 
-    my $bwl = $opts->{bwlimit}*1024; # bandwidth limit for cstream
-    $cmd .= "|cstream -t $bwl" if $opts->{bwlimit};
-    $cmd .= "|$comp" if $comp;
+    push @$tar, @sources;
 
-    $cmd .= ")";
+    my $cmd = [ $tar ];
+
+    my $bwl = $opts->{bwlimit}*1024; # bandwidth limit for cstream
+    push @$cmd, [ 'cstream', '-t', $bwl ] if $opts->{bwlimit};
+    push @$cmd, [ split(/\s+/, $comp) ] if $comp;
 
     if ($opts->{stdout}) {
-       $self->cmd ($cmd, output => ">&" . fileno($opts->{stdout}));
+       push @{$cmd->[-1]}, \(">&" . fileno($opts->{stdout}));
     } else {
-       $self->cmd ("$cmd >$filename");
+       push @{$cmd->[-1]}, \(">" . PVE::Tools::shellquote($filename));
     }
+    $self->cmd($cmd);
 }
 
 sub cleanup {
     my ($self, $task, $vmid) = @_;
 
-    my $diskinfo = $task->{diskinfo};
+    my $conf = PVE::LXC::load_config($vmid);
 
-    if (my $rootdir = $diskinfo->{mountpoint}) {
-       PVE::Tools::run_command(['umount', '-l', '-d', $rootdir]);
-    };
+    if ($task->{mode} ne 'suspend') {
+       my $rootdir = $default_mount_point;
+       my $disks = $task->{disks};
+       foreach my $disk (reverse @$disks) {
+           PVE::Tools::run_command(['umount', '-l', '-d', $disk->{dir}]) if $disk->{dir};
+       }
+    }
 
     if ($task->{cleanup}->{remove_snapshot}) {
        $self->loginfo("remove vzdump snapshot");