[pve-devel] [RFC v2 pve-container 4/4] vzdump: refactor LXC backup

Wolfgang Bumiller w.bumiller at proxmox.com
Mon Sep 7 13:41:00 CEST 2015


*) Use the new Tools::command_pipe instead of building a
   shell command string.
*) Ditch 'find' and utilize the --one-file-system switch
   instead.
*) Added mountpoint handling
*) Added support for 'backup=yes|no' on mountpoints
*) sanitizing mountpoint paths
---
 src/PVE/VZDump/LXC.pm | 195 +++++++++++++++++++++++++++-----------------------
 1 file changed, 105 insertions(+), 90 deletions(-)

diff --git a/src/PVE/VZDump/LXC.pm b/src/PVE/VZDump/LXC.pm
index 77cd9cf..e9a8be8 100644
--- a/src/PVE/VZDump/LXC.pm
+++ b/src/PVE/VZDump/LXC.pm
@@ -16,23 +16,30 @@ 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) = @_;
 
-    $self->loginfo ("starting $text sync $from to $to");
+    my $disks = $task->{disks};
+    my $from = $disks->[0]->{dir} . '/';
 
-    my $starttime = time();
+    $self->loginfo("starting $text sync $from to $to");
 
     my $opts = $self->{vzdump}->{opts};
 
-    my $rsyncopts = "--stats -x -X --numeric-ids";
+    my $base = ['rsync', '--stats', '-x', '-X', '--numeric-ids',
+                '-aH', '--delete', '--no-whole-file', '--inplace'];
 
-    $rsyncopts .= " --bwlimit=$opts->{bwlimit}" if $opts->{bwlimit};
+    push @$base, "--bwlimit=$opts->{bwlimit}" if $opts->{bwlimit};
+    push @$base, map { "--exclude=$_" } @{$self->{vzdump}->{findexcl}};
+    push @$base, map { "--exclude=$_" } @{$task->{exclude_dirs}};
 
-    $self->cmd ("rsync $rsyncopts -aH --delete --no-whole-file --inplace '$from' '$to'");
+    # FIXME: to support --one-file-system we have to make all exclude paths
+    # relative to the current mountpoint
 
-    my $delay = time () - $starttime;
+    my $starttime = time();
+    $self->cmd([@$base, $from, $to]);
+    my $delay = time() - $starttime;
 
-    $self->loginfo ("$text sync finished ($delay seconds)");
+    $self->loginfo("$text sync finished ($delay seconds)");
 };
 
 sub new {
@@ -77,37 +84,54 @@ my $check_mountpoint_empty = sub {
     });
 };
 
+# 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 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};
+    # fixme: when do we deactivate ??
+    PVE::LXC::foreach_mountpoint($conf, sub {
+	my ($name, $data) = @_;
+	my $volid = $data->{volume};
+	my $mount = $data->{mp};
 
-    die "missing root volid (no volid)\n" if !$diskinfo->{volid};
+	$mount = $data->{mp} = sanitize_mountpoint($mount);
 
-    # fixme: when do we deactivate ??
-    PVE::Storage::activate_volumes($self->{storecfg}, [$diskinfo->{volid}]);
+	return if !$volid || !$mount || $volid =~ m|^/|;
+	if ($name ne 'rootfs' && !$data->{backup}) {
+	    push @$exclude_dirs, $mount;
+	    return;
+	}
+
+	push @$disks, $data;
+    });
+    my $volid_list = [map { $_->{volume} } @$disks];
+    PVE::Storage::activate_volumes($storage_cfg, $volid_list);
 
     $self->loginfo("TEST: prepare");
     if ($mode eq 'snapshot') {
-
-	if (!PVE::LXC::has_feature('snapshot', $conf, $self->{storecfg})) {
+	if (!PVE::LXC::has_feature('snapshot', $conf, $storage_cfg)) {
 	    die "mode failure - some volumes does not support snapshots\n";
 	}
 
@@ -116,27 +140,27 @@ sub prepare {
 	    PVE::LXC::snapshot_delete($vmid, 'vzdump', 0);
 	}
 
-	my $mountpoint = $default_mount_point;
-	mkpath $mountpoint;
-	&$check_mountpoint_empty($mountpoint);
+	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);
 	$task->{snapshot_count} = scalar(@$volid_list);
-	
     } elsif ($mode eq 'stop') {
-	my $mountpoint = $default_mount_point;
-	mkpath $mountpoint;
-	&$check_mountpoint_empty($mountpoint);
-
-	my $volid_list = [$diskinfo->{volid}];
-	my $mp = { volume => $diskinfo->{volid}, mp => "/" };
-	PVE::LXC::mountpoint_mount($mp, $mountpoint, $self->{storecfg});
-	$diskinfo->{dir} = $diskinfo->{mountpoint} = $mountpoint;
-	$task->{snapdir} = $diskinfo->{dir};
+	my $rootdir = $default_mount_point;
+	mkpath $rootdir;
+	&$check_mountpoint_empty($rootdir);
+
+	foreach my $disk (@$disks) {
+	    $disk->{dir} = "$rootdir/$disk->{mp}";
+	    PVE::LXC::mountpoint_mount($disk, $rootdir, $storage_cfg);
+	}
+	$task->{snapdir} = $rootdir;
     } 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 {
 	die "unknown mode '$mode'\n"; # should not happen
@@ -158,8 +182,6 @@ sub unlock_vm {
 sub snapshot {
     my ($self, $task, $vmid) = @_;
 
-    my $diskinfo = $task->{diskinfo};
-
     $self->loginfo("create storage snapshot snapshot");
 
     # todo: freeze/unfreeze if we have more than one volid
@@ -171,29 +193,29 @@ sub snapshot {
     die "unable to read vzdump shanpshot 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};
+    my $volid_list = [map { $_->{volume} } @$disks];
 
-    my $mountpoint = $default_mount_point;
-	
-    my $mp = { volume => $diskinfo->{volid}, mp => "/" };
-    PVE::LXC::mountpoint_mount($mp, $mountpoint, $self->{storecfg}, 'vzdump');
- 
-    $diskinfo->{dir} = $diskinfo->{mountpoint} = $mountpoint;
-    $task->{snapdir} = $diskinfo->{dir};
+    my $storage_cfg = $self->{storecfg};
+
+    my $rootdir = $default_mount_point;
+
+    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");
+    $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 {
@@ -237,56 +259,49 @@ sub assemble {
 sub archive {
     my ($self, $task, $vmid, $filename, $comp) = @_;
 
-    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 $opts = $self->{vzdump}->{opts};
 
-    my $taropts = "--totals --sparse --numeric-owner --no-recursion --xattrs --one-file-system";
-
-    # 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
-    #if ($snapdir eq $task->{tmpdir} && $snapdir =~ m|^$opts->{dumpdir}/|) {
-    #       $taropts .= " --remove-files"; # try to save space
-    #}
+    my $cmd = ['tar', 'cpf', '-',
+               '--totals', '--sparse', '--numeric-owner', '--xattrs',
+               '--one-file-system'];
+    push @$cmd, map { "--exclude=.$_" } @{$self->{vzdump}->{findexcl}};
 
-    my $cmd = "(";
+    push @$cmd, '--directory', $tmpdir, './etc/vzdump/pct.conf',
+                '--exclude=./etc/vzdump',
+                '--directory', $snapdir;
 
-    $cmd .= "cd $snapdir;find . $findargs|sed 's/\\\\/\\\\\\\\/g'|";
-    $cmd .= "tar cpf - $taropts ";
-    # The directory parameter can give a 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 -";
+    my $disks = $task->{disks};
+    # mp already starts with a / so we only need to add the dot
+    push @$cmd, map { '.' . $_->{mp} } @$disks;
 
-    my $bwl = $opts->{bwlimit}*1024; # bandwidth limit for cstream
-    $cmd .= "|cstream -t $bwl" if $opts->{bwlimit};
-    $cmd .= "|$comp" if $comp;
+    my @pipe = ($cmd);
+    if (my $bwl = $opts->{bwlimit}*1024) {
+	# bandwidth limit for cstream
+	push @pipe, ['cstream', '-t', $bwl];
+    }
 
-    $cmd .= ")";
+    if ($comp) {
+	push @pipe, [$comp];
+    }
 
-    if ($opts->{stdout}) {
-	$self->cmd ($cmd, output => ">&" . fileno($opts->{stdout}));
-    } else {
-	$self->cmd ("$cmd >$filename");
+    if (!PVE::Tools::command_pipes(undef, $opts->{stdout} || $filename, undef, @pipe)) {
+	die "error in backup process";
     }
 }
 
 sub cleanup {
     my ($self, $task, $vmid) = @_;
 
-    my $diskinfo = $task->{diskinfo};
+    my $conf = PVE::LXC::load_config($vmid);
+
+    my $mountpoint = $default_mount_point;
 
-    if (my $mountpoint = $diskinfo->{mountpoint}) {
-	PVE::Tools::run_command(['umount', '-l', '-d', $mountpoint]);
-    };
+    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");
-- 
2.1.4





More information about the pve-devel mailing list