Changeset d89e4cb491196b13a9746b8ec907a6e38c9a3c09

Show
Ignore:
Timestamp:
07/31/09 14:45:12 (5 years ago)
Author:
Mark Harrison <mark@omniti.com>
git-committer:
Mark Harrison <mark@omniti.com> 1249051512 +0000
git-parent:

[063e7fe614e251d94c72dc19fe91ce1f839bc088], [c1ce6ebe9bb180b515b248a0b62efc120fa10846]

git-author:
Mark Harrison <mark@omniti.com> 1249051512 +0000
Message:

Merge replay -r92:109 into trunk

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • zetaback.in

    r7a79e4a rc1ce6eb  
    11#!/usr/bin/perl 
     2# vim: sts=2 sw=2 ts=8 et 
    23 
    34# Copyright (c) 2007 OmniTI Computer Consulting, Inc. All rights reserved. 
     
    3031$conf{'default'}->{'retention'} = 14 * 86400; 
    3132$conf{'default'}->{'compressionlevel'} = 1; 
     33$conf{'default'}->{'dataset_backup'} = 0; 
    3234 
    3335=pod 
     
    336338key.  There is no default for this setting. 
    337339 
     340=item dataset_backup 
     341 
     342By default zetaback backs zfs filesystems up to files. This option lets you 
     343specify that the backup go be stored as a zfs dataset on the backup host. 
     344 
    338345=back 
    339346 
     
    415422} 
    416423 
     424sub fs_encode($) { 
     425  my $d = shift; 
     426  my @parts = split('@', $d); 
     427  my $e = encode_base64($parts[0], ''); 
     428  $e =~ s/\//_/g; 
     429  $e =~ s/=/-/g; 
     430  $e =~ s/\+/\./g; 
     431  if (exists $parts[1]) { 
     432    $e .= "\@$parts[1]"; 
     433  } 
     434  return $e; 
     435} 
     436sub fs_decode($) { 
     437  my $e = shift; 
     438  $e =~ s/_/\//g; 
     439  $e =~ s/-/=/g; 
     440  $e =~ s/\./\+/g; 
     441  return decode_base64($e); 
     442} 
    417443sub dir_encode($) { 
    418444  my $d = shift; 
     
    471497  my $dir = shift; 
    472498  $info{last_full} = $info{last_incremental} = $info{last_backup} = 0; 
     499  # Look for standard file based backups first 
    473500  opendir(D, $dir) || return \%info; 
    474501  foreach my $file (readdir(D)) { 
     
    492519  } 
    493520  closedir(D); 
     521  # Now look for zfs based backups 
     522  my $storefs; 
     523  eval { 
     524    $storefs = get_fs_from_mountpoint($dir); 
     525  }; 
     526  return \%info if ($@); 
     527  my $rv = open(ZFSLIST, "__ZFS__ list -H -r -t snapshot $storefs |"); 
     528  return \%info unless $rv; 
     529  while (<ZFSLIST>) { 
     530      my @F = split(' '); 
     531      my ($rawfs, $snap) = split('@', $F[0]); 
     532      my ($whence) = ($snap =~ /(\d+)/); 
     533      next unless $whence; 
     534      my @fsparts = split('/', $rawfs); 
     535      my $fs = fs_decode($fsparts[-1]); 
     536      # Treat a dataset backup as a full backup from the point of view of the 
     537      # backup lists 
     538      $info{$fs}->{full}->{$whence}->{'snapshot'} = $snap; 
     539      $info{$fs}->{full}->{$whence}->{'dataset'} = "$rawfs\@$snap"; 
     540      # Note - this field isn't set for file backups - we probably should do 
     541      # this 
     542      $info{$fs}->{full}->{$whence}->{'pretty_size'} = "$F[1]"; 
     543      $info{$fs}->{last_full} = $whence if ($whence > 
     544          $info{$fs}->{last_full}); 
     545      $info{$fs}->{last_backup} = $whence if ($whence > 
     546          $info{$fs}->{last_backup}); 
     547  } 
     548  close(ZFSLIST); 
     549 
    494550  return \%info; 
    495551} 
     
    522578 
    523579# Lots of args.. internally called. 
    524 sub zfs_do_backup($$$$$$) { 
    525   my ($host, $fs, $type, $point, $store, $dumpfile) = @_; 
     580sub zfs_do_backup($$$$$$;$) { 
     581  my ($host, $fs, $type, $point, $store, $dumpname, $base) = @_; 
     582  my ($storefs, $encodedname); 
    526583  my $agent = config_get($host, 'agent'); 
    527584  my $ssh_config = config_get($host, 'ssh_config'); 
     
    529586  print "Using custom ssh config file: $ssh_config\n" if($DEBUG); 
    530587 
     588  # compression is meaningless for dataset backups 
     589  if ($type ne "s") { 
     590    my $cl = config_get($host, 'compressionlevel'); 
     591    if ($cl >= 1 && $cl <= 9) { 
     592        open(LBACKUP, "|gzip -$cl >$store/.$dumpname") || 
     593        die "zfs_full_backup: cannot create dump\n"; 
     594    } else { 
     595        open(LBACKUP, ">$store/.$dumpname") || 
     596        die "zfs_full_backup: cannot create dump\n"; 
     597    } 
     598  } else { 
     599    # Dataset backup - pipe received filesystem to zfs recv 
     600    eval { 
     601      $storefs = get_fs_from_mountpoint($store); 
     602    }; 
     603    if ($@) { 
     604      # The zfs filesystem doesn't exist, so we have to work out what it 
     605      # would be 
     606      my $basestore = config_get($host, 'store'); 
     607      $basestore =~ s/\/?%h//g; 
     608      $storefs = get_fs_from_mountpoint($basestore); 
     609      $storefs="$storefs/$host"; 
     610    } 
     611    $encodedname = fs_encode($dumpname); 
     612    print STDERR "Receiving to zfs filesystem $storefs/$encodedname\n" 
     613      if($DEBUG); 
     614    zfs_create_intermediate_filesystems("$storefs/$encodedname"); 
     615    open(LBACKUP, "|__ZFS__ recv $storefs/$encodedname"); 
     616  } 
    531617  # Do it. yeah. 
    532   my $cl = config_get($host, 'compressionlevel'); 
    533   if ($cl >= 1 && $cl <= 9) { 
    534     open(LBACKUP, "|gzip -$cl >$store/.$dumpfile") || 
    535       die "zfs_full_backup: cannot create dump\n"; 
    536   } else { 
    537     open(LBACKUP, ">$store/.$dumpfile") || 
    538       die "zfs_full_backup: cannot create dump\n"; 
    539   } 
    540618  eval { 
    541619    if(my $pid = fork()) { 
     
    545623    } 
    546624    else { 
    547       my @cmd = ('ssh', split(/ /, $ssh_config), $host, $agent, '-z', $fs, "-$type", $point); 
     625      my @cmd = ('ssh', split(/ /, $ssh_config), $host, $agent, '-z', $fs); 
     626      if ($type eq "i" || ($type eq "s" && $base)) { 
     627        push @cmd, ("-i", $base); 
     628      } 
     629      if ($type eq "f" || $type eq "s") { 
     630        push @cmd, ("-$type", $point); 
     631      } 
    548632      open STDIN, "/dev/null" || exit(-1); 
    549633      open STDOUT, ">&LBACKUP" || exit(-1); 
     634      print STDERR "   => @cmd\n" if($DEBUG); 
    550635      exec { $cmd[0] } @cmd; 
    551636      print STDERR "$cmd[0] failed: $?\n"; 
    552637      exit($?); 
    553638    } 
    554     die "dump failed (zero bytes)\n" if(-z "$store/.$dumpfile"); 
    555     rename("$store/.$dumpfile", "$store/$dumpfile") || die "cannot rename dump\n"; 
     639    if ($type ne "s") { 
     640      die "dump failed (zero bytes)\n" if(-z "$store/.$dumpname"); 
     641      rename("$store/.$dumpname", "$store/$dumpname") || die "cannot rename dump\n"; 
     642    } else { 
     643      # Check everything is ok 
     644      `__ZFS__ list $storefs/$encodedname`; 
     645      die "dump failed (received snapshot $storefs/$encodedname does not exist)\n" 
     646        if $?; 
     647    } 
    556648  }; 
    557649  if($@) { 
    558     unlink("$store/.$dumpfile"); 
     650    if ($type ne "s") { 
     651        unlink("$store/.$dumpname"); 
     652    } 
    559653    chomp(my $error = $@); 
    560654    $error =~ s/[\r\n]+/ /gsm; 
     
    562656    die "zfs_full_backup: failed $@"; 
    563657  } 
    564   my @st = stat("$store/$dumpfile"); 
    565   my $size = pretty_size($st[7]); 
     658  my $size; 
     659  if ($type ne "s") { 
     660    my @st = stat("$store/$dumpname"); 
     661    $size = pretty_size($st[7]); 
     662  } else { 
     663    $size = `__ZFS__ get -Ho value used $storefs/$encodedname`; 
     664    chomp $size; 
     665  } 
    566666  zetaback_log($host, "SUCCESS[$size] $host:$fs $type\n"); 
     667} 
     668 
     669sub zfs_create_intermediate_filesystems($) { 
     670  my ($fs) = @_; 
     671  my $idx=0; 
     672  while (($idx = index($fs, '/', $idx+1)) != -1) { 
     673      my $fspart = substr($fs, 0, $idx); 
     674      `__ZFS__ list $fspart 2>&1`; 
     675      if ($?) { 
     676        print STDERR "Creating intermediate zfs filesystem: $fspart\n" 
     677          if $DEBUG; 
     678        `__ZFS__ create $fspart`; 
     679      } 
     680  } 
    567681} 
    568682 
     
    570684  my ($host, $fs, $store) = @_; 
    571685 
    572   # Translate into a proper dumpfile nameA 
     686  # Translate into a proper dumpname 
    573687  my $point = time(); 
    574688  my $efs = dir_encode($fs); 
    575   my $dumpfile = "$point.$efs.full"; 
    576  
    577   zfs_do_backup($host, $fs, 'f', $point, $store, $dumpfile); 
     689  my $dumpname = "$point.$efs.full"; 
     690 
     691  zfs_do_backup($host, $fs, 'f', $point, $store, $dumpname); 
    578692} 
    579693 
     
    582696  my $agent = config_get($host, 'agent'); 
    583697 
    584   # Translate into a proper dumpfile nameA 
     698  # Translate into a proper dumpname 
    585699  my $point = time(); 
    586700  my $efs = dir_encode($fs); 
    587   my $dumpfile = "$point.$efs.incremental.$base"; 
    588  
    589   zfs_do_backup($host, $fs, 'i', $base, $store, $dumpfile); 
     701  my $dumpname = "$point.$efs.incremental.$base"; 
     702 
     703  zfs_do_backup($host, $fs, 'i', $point, $store, $dumpname, $base); 
     704
     705 
     706sub zfs_dataset_backup($$$$) { 
     707  my ($host, $fs, $base, $store) = @_; 
     708  my $agent = config_get($host, 'agent'); 
     709 
     710  my $point = time(); 
     711  my $dumpname = "$fs\@$point"; 
     712 
     713  zfs_do_backup($host, $fs, 's', $point, $store, $dumpname, $base); 
    590714} 
    591715 
     
    631755    if($DEBUG) { 
    632756      my $tf = config_get($host, 'time_format'); 
    633       print "    => I can remove:\n"; 
     757      print "    => Candidates for removal:\n"; 
    634758      foreach (@backup_points) { 
    635759        print "      => ". strftime($tf, localtime($_)); 
    636760        print " [". (exists($info->{full}->{$_}) ? "full":"incremental") ."]"; 
    637         print " XXX" if(!exists($must_save{$_})); 
     761        print " => marked for removal" if(!exists($must_save{$_})); 
    638762        print "\n"; 
    639763      } 
     
    642766      my $efs = dir_encode($disk); 
    643767      my $filename; 
    644       if(exists($info->{full}->{$_})) { 
    645         $filename = "$store/$_.$efs.full"; 
    646       } 
    647       elsif(exists($info->{incremental}->{$_})) { 
    648         $filename = "$store/$_.$efs.incremental.$info->{incremental}->{$_}->{depends}"; 
    649       } 
    650       else { 
     768      my $dataset; 
     769      if(exists($info->{full}->{$_}->{file})) { 
     770        $filename = $info->{full}->{$_}->{file}; 
     771      } elsif(exists($info->{incremental}->{$_}->{file})) { 
     772        $filename = $info->{full}->{$_}->{file}; 
     773      } elsif(exists($info->{full}->{$_}->{dataset})) { 
     774        $dataset = $info->{full}->{$_}->{dataset}; 
     775      } elsif(exists($info->{incremental}->{$_}->{dataset})) { 
     776        $dataset = $info->{incremental}->{$_}->{dataset}; 
     777      } else { 
    651778        print "ERROR: We tried to expunge $host $disk [$_], but couldn't find it.\n"; 
    652779      } 
    653       print "    => expunging $filename\n" if($DEBUG); 
     780      print "    => expunging ${filename}${dataset}\n" if($DEBUG); 
    654781      unless($NEUTERED) { 
    655         unlink($filename) || print "ERROR: unlink $filename: $?\n"; 
     782        if ($filename) { 
     783          unlink($filename) || print "ERROR: unlink $filename: $?\n"; 
     784        } elsif ($dataset) { 
     785          `__ZFS__ destroy $dataset`; 
     786          if ($?) { 
     787            print "ERROR: zfs destroy $dataset: $?\n"; 
     788          } 
     789        } 
    656790      } 
    657791    } 
     
    702836  } 
    703837  return @list; 
     838} 
     839 
     840sub get_fs_from_mountpoint($) { 
     841    my ($mountpoint) = @_; 
     842    my $fs; 
     843    my $rv = open(ZFSLIST, "__ZFS__ list -t filesystem -H |"); 
     844    die "Unable to determine zfs filesystem for $mountpoint" unless $rv; 
     845    while (<ZFSLIST>) { 
     846        my @F = split(' '); 
     847        if ($F[-1] eq $mountpoint) { 
     848            $fs = $F[0]; 
     849            last; 
     850        } 
     851    } 
     852    close(ZFSLIST); 
     853    die "Unable to determine zfs filesystem for $mountpoint" unless $fs; 
     854    return $fs; 
    704855} 
    705856 
     
    784935 
    785936  foreach(@backup_list) { 
    786     $_->{success} = zfs_restore_part($RESTORE_HOST, $RESTORE_ZFS, $_->{file}, $_->{depends}); 
    787   } 
    788 
    789  
    790 sub zfs_restore_part($$$;$) { 
    791   my ($host, $fs, $file, $dep) = @_; 
     937    $_->{success} = zfs_restore_part($RESTORE_HOST, $RESTORE_ZFS, $_->{file}, $_->{dataset}, $_->{depends}); 
     938  } 
     939
     940 
     941sub zfs_restore_part($$$$;$) { 
     942  my ($host, $fs, $file, $dataset, $dep) = @_; 
     943  unless ($file || $dataset) { 
     944    print STDERR "=> No dataset or filename given to restore. Bailing out."; 
     945    return 1; 
     946  } 
    792947  my $ssh_config = config_get($host, 'ssh_config'); 
    793948  $ssh_config = "-F $ssh_config" if($ssh_config); 
     
    802957    $command = "__ZFS__ recv $fs"; 
    803958  } 
    804   print " => piping $file to $command\n" if($DEBUG); 
    805   if($NEUTERED) { 
    806     print "gzip -dfc $file | ssh $ssh_config $host $command\n" if ($DEBUG); 
    807   } 
    808   else { 
    809     open(DUMP, "gzip -dfc $file |"); 
     959  if ($file) { 
     960    print " => piping $file to $command\n" if($DEBUG); 
     961    print "gzip -dfc $file | ssh $ssh_config $host $command\n" if ($DEBUG && $NEUTERED); 
     962  } elsif ($dataset) { 
     963    print " => piping $dataset to $command using zfs send\n" if ($DEBUG); 
     964    print "zfs send $dataset | ssh $ssh_config $host $command\n" if ($DEBUG && $NEUTERED); 
     965  } 
     966  unless($NEUTERED) { 
     967    if ($file) { 
     968      open(DUMP, "gzip -dfc $file |"); 
     969    } elsif ($dataset) { 
     970      open(DUMP, "__ZFS__ send $dataset |"); 
     971    } 
    810972    eval { 
    811973      open(RECEIVER, "| ssh $ssh_config $host $command"); 
     
    828990  print "\t" . strftime($tf, localtime($point)) . " [$point] "; 
    829991  if(exists($info->{full}->{$point})) { 
    830     my @st = stat($info->{full}->{$point}->{file}); 
    831     print "FULL " . pretty_size($st[7]); 
    832     print "\n\tfile: $info->{full}->{$point}->{file}" if($SHOW_FILENAMES); 
     992    if ($info->{full}->{$point}->{file}) { 
     993      my @st = stat($info->{full}->{$point}->{file}); 
     994      print "FULL " . pretty_size($st[7]); 
     995      print "\n\tfile: $info->{full}->{$point}->{file}" if($SHOW_FILENAMES); 
     996    } elsif ($info->{full}->{$point}->{dataset}) { 
     997      print "FULL $info->{full}->{$point}->{pretty_size}"; 
     998      print "\n\tdataset: $info->{full}->{$point}->{dataset}" 
     999        if($SHOW_FILENAMES); 
     1000    } 
    8331001  } else { 
    8341002    my @st = stat($info->{incremental}->{$point}->{file}); 
     
    8431011  my $backup_info = scan_for_backups($store); 
    8441012  my $tf = config_get($host, 'time_format'); 
    845   my @files
     1013  my (@files, @datasets)
    8461014  foreach my $disk (sort keys %{$backup_info}) { 
    8471015    my $info = $backup_info->{$disk}; 
     
    8811049    foreach (@backup_points) { 
    8821050      pretty_print_backup($info, $host, $_); 
    883       push @files, exists($info->{full}->{$_}) ? $info->{full}->{$_}->{file} : $info->{incremental}->{$_}->{file}; 
     1051      if(exists($info->{full}->{$_}->{file})) { 
     1052        push @files, $info->{full}->{$_}->{file}; 
     1053      } elsif(exists($info->{incremental}->{$_}->{file})) { 
     1054        push @files, $info->{incremental}->{$_}->{file}; 
     1055      } elsif(exists($info->{full}->{$_}->{dataset})) { 
     1056        push @datasets, $info->{full}->{$_}->{dataset} 
     1057      } 
    8841058    } 
    8851059    print "\n"; 
    8861060  } 
    887   if($ARCHIVE && scalar(@files)) { 
    888     my $archive = config_get($host, 'archive'); 
    889     $archive =~ s/%h/$host/g; 
    890     if(! -d $archive) { 
    891       mkdir $archive || die "Cannot mkdir($archive)\n"; 
    892     } 
    893     print "\nAre you sure you would like to archive ".scalar(@files)." file(s)? "; 
     1061  if($ARCHIVE && (scalar(@files) || scalar(@datasets))) { 
     1062    print "\nAre you sure you would like to archive ".scalar(@files). 
     1063      " file(s) and ".scalar(@datasets)." dataset(s)? "; 
    8941064    while(($_ = <>) !~ /(?:y|n|yes|no)$/i) { 
    895       print "Are you sure you would like to archive ".scalar(@files)." file(s)? "; 
     1065      print "\nAre you sure you would like to archive ".scalar(@files). 
     1066        " file(s) and ".scalar(@datasets)." dataset(s)? "; 
    8961067    } 
    8971068    if(/^y/i) { 
    898       foreach my $file (@files) { 
    899         (my $afile = $file) =~ s/^$store/$archive/; 
    900         move($file, $afile) || print "Error archiving $file: $!\n"; 
     1069      if (@files) { 
     1070        my $archive = config_get($host, 'archive'); 
     1071        $archive =~ s/%h/$host/g; 
     1072        if(! -d $archive) { 
     1073          mkdir $archive || die "Cannot mkdir($archive)\n"; 
     1074        } 
     1075        foreach my $file (@files) { 
     1076          (my $afile = $file) =~ s/^$store/$archive/; 
     1077          move($file, $afile) || print "Error archiving $file: $!\n"; 
     1078        } 
     1079      } 
     1080      if (@datasets) { 
     1081        my $archive = config_get($host, 'archive'); 
     1082        my $storefs = get_fs_from_mountpoint($store); 
     1083        (my $basearchive = $archive) =~ s/\/?%h//g; 
     1084        my $basearchivefs; 
     1085        eval { 
     1086          $basearchivefs = get_fs_from_mountpoint($basearchive); 
     1087        }; 
     1088        die "Unable to find archive filesystem. The archive directory must be the root of a zfs filesystem to archive datasets." if $@; 
     1089        my $archivefs = "$basearchivefs/$host"; 
     1090        `__ZFS__ create $archivefs`; # We don't care if this fails 
     1091        my %seen = (); 
     1092        foreach my $dataset (@datasets) { 
     1093          $dataset =~ s/@.*$//; # Only rename filesystems, not snapshots 
     1094          next if $seen{$dataset}++; # Only rename a filesystem once 
     1095          (my $adataset = $dataset) =~ s/^$storefs/$archivefs/; 
     1096          `__ZFS__ rename $dataset $adataset`; 
     1097          if ($?) { 
     1098            print "Error archiving $dataset\n"; 
     1099          } 
     1100        } 
    9011101      } 
    9021102    } 
     
    9181118    # We need a lock for the listing. 
    9191119    return unless(lock($host, ".list")); 
     1120 
     1121    # Get list of zfs filesystems from the agent 
    9201122    open(SILENT, ">&", \*STDERR); 
    9211123    close(STDERR); 
     
    9571159        $backup_type = 'full'; 
    9581160      } 
    959    
    9601161      # If we want an incremental, but have no full, then we need to upgrade to full 
    9611162      if($backup_type eq 'incremental') { 
     
    9691170      $backup_type = 'full' if($FORCE_FULL); 
    9701171      $backup_type = 'incremental' if($FORCE_INC); 
     1172      $backup_type = 'dataset' if(config_get($host, 'dataset_backup') eq 1 && 
     1173        $backup_type ne 'no'); 
    9711174 
    9721175      print " => doing $backup_type backup\n" if($DEBUG); 
     
    10061209          } 
    10071210        } 
     1211        if($backup_type eq 'dataset') { 
     1212          my @backups = sort { $b <=> $a } (keys %{$backup_info->{'full'}}); 
     1213          eval { zfs_dataset_backup($host, $diskname, $backups[0], $store); }; 
     1214          if ($@) { 
     1215            chomp(my $err = $@); 
     1216            print " => failure $err\n"; 
     1217          } 
     1218          else { 
     1219            # Unless there was an error backing up, remove all the other dset snaps 
     1220            foreach (keys %snaps) { 
     1221              zfs_remove_snap($host, $diskname, $_) if(/^__zb_dset_(\d+)/)  
     1222            } 
     1223          } 
     1224          $took_action = 1; 
     1225        } 
    10081226        unlock($host, dir_encode($diskname), 1); 
    10091227      } 
  • zetaback_agent.in

    r063e7fe r1c9960f  
    1212use vars qw/%conf $version_string 
    1313            $PREFIX $CONF $LIST $FULL $SNAP $ZFS $BASE $RESTORE $VERSION 
    14             $BUG_6343779 $NEEDSFD/; 
     14            $BUG_6343779 $NEEDSFD $DSET/; 
    1515$version_string = '0.1'; 
    1616$PREFIX = q^__PREFIX__^; 
     
    4848  "f=s" => \$FULL, 
    4949  "i=s" => \$BASE, 
     50  "s=s" => \$DSET, 
    5051  "b=s" => \$BUG_6343779, 
    5152  "v"   => \$VERSION, 
     
    8687Perform an incremental backup.  The name of the backup will include  
    8788<timestamp>, which is provided by the backup server. 
     89 
     90=item -s <timestamp> 
     91 
     92Perform a dataset backup.  The name of the backup will include  
     93<timestamp>, which is provided by the backup server. This requires the -i 
     94option to specify the base dataset the expected by the backup server. 
    8895 
    8996=item -l 
     
    190197  die "zfs_agent_remove_snap: insufficient args\n" unless($ZFS && $SNAP); 
    191198  if($SNAP eq '__zb_incr' or 
    192      $SNAP =~ /__zb_full_\d+/) { 
     199     $SNAP =~ /__zb_full_\d+/ or 
     200     $SNAP =~ /__zb_dset_\d+/) { 
    193201    $target .= $SNAP; 
    194202  } 
     
    222230  `__ZFS__ snapshot $target`; 
    223231  my @cmd = ("__ZFS__", "send", "-i", $base, $target); 
     232  if($NEEDSFD) { 
     233    fifo_exec(@cmd); 
     234  } else { 
     235    exec { $cmd[0] } @cmd; 
     236  } 
     237  exit; 
     238} 
     239 
     240sub zfs_agent_perform_dataset { 
     241  my $target = $ZFS . '@__zb_dset_' . $DSET; 
     242  my $base = $ZFS . '@__zb_dset_' . $BASE; 
     243  unless($ZFS && $DSET) { 
     244    die "zfs_agent_perform_dataset: bad args\n" 
     245  } 
     246  `__ZFS__ snapshot $target`; 
     247  # $BASE (the base snapshot) is optional. If provided, send an incremental 
     248  # snapshot 
     249  my @cmd; 
     250  if ($BASE) { 
     251    @cmd = ("__ZFS__", "send", "-i", $base, $target); 
     252  } else { 
     253    @cmd = ("__ZFS__", "send", $target); 
     254  } 
    224255  if($NEEDSFD) { 
    225256    fifo_exec(@cmd); 
     
    295326if($ZFS && $RESTORE) { zfs_agent_perform_restore(); exit; } 
    296327if($ZFS && $FULL) { zfs_agent_perform_full(); exit; } 
     328if($ZFS && $DSET) { zfs_agent_perform_dataset(); exit; } 
    297329if($ZFS && $BASE) { zfs_agent_perform_incremental(); exit; } 
    298330