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