*) 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 _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel