plugin-dump
changeset 2 9c602f1fe522
parent 1 f89626b4dda9
child 3 75bddaf5ed89
equal deleted inserted replaced
1:f89626b4dda9 2:9c602f1fe522
     1 #! /usr/bin/perl
       
     2 use 5.010;
       
     3 use strict;
       
     4 use warnings;
       
     5 use Pod::Usage;
       
     6 use Getopt::Long;
       
     7 use Readonly;
       
     8 
       
     9 our $VERSION = '0.01';
       
    10 
       
    11 use constant YES       => 'YES';
       
    12 use constant NO        => 'NO';
       
    13 use constant DUMPDATES => '/var/lib/dumpdates';
       
    14 use constant FD3 => 3;
       
    15 use constant FD4 => 4;
       
    16 
       
    17 Readonly my %SUPPORT => (
       
    18     CONFIG          => YES,    # --config … (ignored?)
       
    19     HOST            => YES,    # --host …   (ignored?)
       
    20     DISK            => YES,    # --disk …   (ignored?)
       
    21     MAX_LEVEL       => 9,
       
    22     CLIENT_ESTIMATE => YES,    # estimate
       
    23     CALCSIZE        => YES,    # estimate --calcsize
       
    24     MESSAGE_LINE    => YES,    # --message line
       
    25     INDEX_LINE      => NO,     # --index line
       
    26     RECORD          => YES,    # --record
       
    27 );
       
    28 
       
    29 # the commands we need to support as required by the
       
    30 # API:  http://wiki.zmanda.com/index.php/Application_API/Operations
       
    31 
       
    32 sub exit_support;
       
    33 sub exit_selfcheck;
       
    34 sub exit_estimate;
       
    35 sub exit_backup;
       
    36 
       
    37 # some helper functions
       
    38 
       
    39 sub device;
       
    40 sub OK;
       
    41 sub ERROR(@);
       
    42 
       
    43 # bad but common style - the global options
       
    44 
       
    45 my $opt_config;      # $config
       
    46 my $opt_host;        # $host
       
    47 my $opt_disk;        # $disk DLE[1]
       
    48 my $opt_device;      # $device DLE[2]
       
    49 my $opt_message;     # line / <>
       
    50 my $opt_index;       # line / <>
       
    51 my $opt_record;      # true / <>
       
    52 my $opt_level;       # 0…99
       
    53 my $opt_calcsize;    # true / <>
       
    54 
       
    55 my $opt_dumpdates;
       
    56 
       
    57 MAIN: {
       
    58     say "$0 @ARGV" if -t STDOUT;
       
    59 
       
    60     my @argv = @ARGV;
       
    61     my $command = shift // pod2usage;
       
    62     GetOptions(
       
    63 
       
    64         'config=s' => \$opt_config,
       
    65         'host=s'   => \$opt_host,      # --host $host
       
    66         'disk=s'   => \$opt_disk,      # --disk $disk
       
    67         'device=s' => \$opt_device,    # --device $device
       
    68 
       
    69         'message=s' => \$opt_message,  # --message line
       
    70         'index=s'   => \$opt_index,    # --index line
       
    71         'record!'   => \$opt_record,   # --record
       
    72         'level=i'   => \$opt_level,    # --level n
       
    73         'calcsize!' => \$opt_calcsize,
       
    74 
       
    75         'dumpdates=s' => \$opt_dumpdates,    # --dumpdates <file>
       
    76     ) or pod2usage(-message => "$0 @argv");
       
    77 
       
    78     given ($command) {
       
    79         when ("support")   { exit_support }
       
    80         when ("selfcheck") { exit_selfcheck }
       
    81         when ("estimate")  { exit_estimate }
       
    82         when ("backup")    { exit_backup }
       
    83     }
       
    84     pod2usage(-message => "$0 @argv");
       
    85 }
       
    86 
       
    87 # output a list of supported options
       
    88 sub exit_support {
       
    89     print map {
       
    90         map { y/_/-/; $_ }
       
    91           $_
       
    92           . " $SUPPORT{$_}\n"
       
    93     } keys %SUPPORT;
       
    94     exit 0;
       
    95 }
       
    96 
       
    97 sub exit_selfcheck {
       
    98 
       
    99     if ($_ = (grep { -x ($_ .= "/dump") } split /:/ => $ENV{PATH})[0]) {
       
   100         OK "dump is \"$_\"";
       
   101     }
       
   102     else { say "ERROR dump not found in $ENV{PATH}\n" }
       
   103 
       
   104     # check the device
       
   105     # the opt_disk is just a label, the device is in opt_device!
       
   106     my $device = device($opt_device);
       
   107 
       
   108     if    (-b $device) { OK "$opt_device ($device is block special)" }
       
   109     elsif (-d $device) { OK "$opt_device ($device is directory)" }
       
   110     else               { ERROR "$opt_device not recognized" }
       
   111 
       
   112     # check the dumpdates file
       
   113     if ($opt_record) {
       
   114         my $dumpdates = $opt_dumpdates ? expand($opt_dumpdates) : DUMPDATES;
       
   115 
       
   116         eval { open(my $x, "+>>", $dumpdates) or die "$!\n" };
       
   117         if   (chomp $@) { ERROR "dumpdates file \"$dumpdates\": $@" }
       
   118         else            { OK "dumpdates file: \"$dumpdates\"" }
       
   119     }
       
   120 
       
   121     exit 0;
       
   122 }
       
   123 
       
   124 sub exit_estimate {
       
   125 
       
   126     # $opt_level, $opt_device
       
   127     my @cmd = (
       
   128         dump => "-$opt_level",
       
   129         "-S",
       
   130         $opt_record && $opt_dumpdates ? (-D => expand($opt_dumpdates)) : (),
       
   131         device($opt_device),
       
   132     );
       
   133 
       
   134     chomp(my $output = `@cmd 2>&1`);
       
   135 
       
   136     if ($? or $output !~ /^\d+/) {
       
   137         say "unexpected output \"$output\"";
       
   138         exit 1;
       
   139     }
       
   140 
       
   141     $output /= 1024;    # to get 1K blocks
       
   142 
       
   143     # level blocks blocksize
       
   144     # --> the blocksize unit is K
       
   145     say "$opt_level $output 1";
       
   146     exit 0;
       
   147 }
       
   148 
       
   149 sub exit_backup {
       
   150 
       
   151     # fd1: data channel
       
   152     # fd3: message channel
       
   153     # fd4: index channel
       
   154 
       
   155     my @dump = (
       
   156         dump => "-$opt_level",
       
   157         -f   => "-",
       
   158         $opt_record ? "-u" : (),
       
   159         $opt_record && $opt_dumpdates ? (-D => expand($opt_dumpdates)) : (),
       
   160         device($opt_device)
       
   161     );
       
   162 
       
   163     # messages ----------,
       
   164     #   ,---------> fd2 ----> fd3
       
   165     # dump --o----> fd1                (data)
       
   166     #         `---> restore -t --> fd4 (index)
       
   167 
       
   168     open(my $msg, ">&=", FD3) or die "Can't open fd3: $!\n";
       
   169     open(my $idx, ">&=", FD4) or die "Can't open fd4: $!\n" if $opt_index;
       
   170 
       
   171     if ($opt_index) {
       
   172 	my $pid = fork // die "Can't fork: $!\n";
       
   173 	if (not $pid) {
       
   174 	    open(STDOUT, "|-") or do {
       
   175 		open(my $restore, "|-") or do {
       
   176 		    open(STDOUT, "|-") or do {
       
   177 			select($idx);
       
   178 			postprocess_toc();
       
   179 			exit 0;
       
   180 		    };
       
   181 		    exec "restore", "-tvf" => "-";
       
   182 		    die "Can't exec `restore -tvf -`: $!";
       
   183 		};
       
   184 		local $/ = 2**16;
       
   185 		while (<STDIN>) {
       
   186 		    print $_;
       
   187 		    print $restore $_;
       
   188 		};
       
   189 		exit 0;
       
   190 	    };
       
   191 
       
   192 	    open(STDERR, "|-") or do {
       
   193 		select($msg);
       
   194 		postprocess_dump_messages();
       
   195 		exit 0;
       
   196 	    };
       
   197 
       
   198 
       
   199 	    exec @dump;
       
   200 	    die "Can't exec `@dump`: $!\n";
       
   201 	}
       
   202 
       
   203 	waitpid($pid, 0);
       
   204 	exit $?;
       
   205     }
       
   206 
       
   207     # no need to send an index
       
   208     my $pid = fork // die "Can't fork: $!\n";
       
   209     if (not $pid) {
       
   210 	open(STDERR, "|-") or do {
       
   211 	    select($msg);
       
   212 	    postprocess_dump_messages();
       
   213 	    exit 0;
       
   214 	};
       
   215 	exec @dump;
       
   216 	die "Can't exec `@dump`: $!\n";
       
   217     }
       
   218     waitpid($pid, 0);
       
   219     exit $?;
       
   220 
       
   221 }
       
   222 
       
   223 sub postprocess_dump_messages() {
       
   224     while (<STDIN>) {
       
   225         print "| $_";
       
   226 
       
   227         if (/^\s+DUMP: (\d+) blocks?/) {
       
   228 
       
   229             # we assume a block size of 1K
       
   230             say "sendbackup: size $1";
       
   231         }
       
   232         elsif (/^\s+DUMP: DUMP IS DONE/) {
       
   233             say "sendbackup: end";
       
   234         }
       
   235     }
       
   236 }
       
   237 
       
   238 sub postprocess_toc() {
       
   239     # dir  4711 ./aaa
       
   240     # leaf 4712 ./bbb/xxx
       
   241     # leaf 4713 ./bbb/a
       
   242     # b
       
   243     # leaf 8819 ./bbb/x
       
   244 
       
   245     my $name;
       
   246 
       
   247     while (<STDIN>) {
       
   248 	chomp;
       
   249 	if (/^(dir|leaf)\s+\d+\s+(\.\/.*)/) {
       
   250 	    say $name if defined $name;
       
   251 	    $name = $2 . ($1 eq "dir" ? "/" : "");
       
   252 	    next;
       
   253 	}
       
   254 
       
   255 	if ($name) {
       
   256 	    $name .= $_;
       
   257 	    next;
       
   258 	}
       
   259 	
       
   260     }
       
   261 
       
   262     say $name if defined $name;
       
   263 
       
   264 }
       
   265 
       
   266 sub device($) {
       
   267     my $_ = shift;
       
   268     return $_ if /^\//;
       
   269     return "/dev/$_";
       
   270 }
       
   271 
       
   272 sub expand($) {
       
   273     my $_ = shift;
       
   274     s/\${c}/$opt_config/g;
       
   275     return $_;
       
   276 }
       
   277 
       
   278 sub OK(@)    { say "OK ",    @_ }
       
   279 sub ERROR(@) { say "ERROR ", @_ }
       
   280 
       
   281 =head1 NAME
       
   282 
       
   283   amdump - the amanda dump application
       
   284 
       
   285 =head1 SYNOPSIS
       
   286 
       
   287   amdump support|selfcheck [options]
       
   288 
       
   289 =head1 COMMANDS
       
   290 
       
   291 =over
       
   292 
       
   293 =item B<support>
       
   294 
       
   295 Send a list of supported features.
       
   296 
       
   297 =back
       
   298 
       
   299 =head1 OPTIONS
       
   300 
       
   301 =head2 Common Options
       
   302 
       
   303 The following options have to be supported by the application.
       
   304 
       
   305 =over 4
       
   306 
       
   307 =item B<--config> I<config>
       
   308 
       
   309 The configuration to be used (the backup set).
       
   310 
       
   311 =item B<--host> I<host>
       
   312 
       
   313 The host from the DLE.
       
   314 
       
   315 =item B<--disk> I<disk>
       
   316 
       
   317 The disk to be saved. It's some "label" for the device to be backed up.
       
   318 
       
   319 =item B<--device> I<device>
       
   320 
       
   321 The device to be backed up (may be a device name, a mountpoint).
       
   322 
       
   323 =back
       
   324 
       
   325 =head2 Optional options
       
   326 
       
   327 The following options need to be supported if indicated by the "support"
       
   328 command.
       
   329 
       
   330 =over
       
   331 
       
   332 =item B<--message> "line"
       
   333 
       
   334 Send messages line by line.
       
   335 
       
   336 =item B<--index> "line"
       
   337 
       
   338 Send the index line by line.
       
   339 
       
   340 =back
       
   341 
       
   342 =cut
       
   343 
       
   344 =head2 Properties
       
   345 
       
   346 =over 4
       
   347 
       
   348 =item B<--dumpdates> I<dumpdates>
       
   349 
       
   350 The location of the dumpdates file. Placeholder "${c}" is allowed and
       
   351 replaced by the name of the current config.
       
   352 
       
   353 =back
       
   354 
       
   355 # vim:sts=4 sw=4 aw ai sm: