[pve-devel] [RFC pve-container 4/4] vzdump: refactor LXC backup
Wolfgang Bumiller
w.bumiller at proxmox.com
Mon Sep 7 12:27:16 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