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 
       
     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 exec_support;
       
    33 sub exec_selfcheck;
       
    34 sub exec_estimate;
       
    35 sub exec_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")   { exec_support }
       
    80         when ("selfcheck") { exec_selfcheck }
       
    81         when ("estimate")  { exec_estimate }
       
    82         when ("backup")    { exec_backup }
       
    83     }
       
    84     pod2usage(-message => "$0 @argv");
       
    85 }
       
    86 
       
    87 # output a list of supported options
       
    88 sub exec_support {
       
    89     print map { "$_ $SUPPORT{$_}\n" =~ s/_/-/gr } keys %SUPPORT;
       
    90     exit 0;
       
    91 }
       
    92 
       
    93 sub exec_selfcheck {
       
    94 
       
    95     if ($_ = (grep { -x ($_ .= "/dump") } split /:/ => $ENV{PATH})[0]) {
       
    96         OK "dump is \"$_\"";
       
    97     }
       
    98     else { say "ERROR dump not found in $ENV{PATH}\n" }
       
    99 
       
   100     # check the device
       
   101     # the opt_disk is just a label, the device is in opt_device!
       
   102     my $device = device($opt_device);
       
   103 
       
   104     if    (-b $device) { OK "$opt_device ($device is block special)" }
       
   105     elsif (-d $device) { OK "$opt_device ($device is directory)" }
       
   106     else               { ERROR "$opt_device not recognized" }
       
   107 
       
   108     # check the dumpdates file
       
   109     if ($opt_record) {
       
   110         my $dumpdates = $opt_dumpdates ? expand($opt_dumpdates) : DUMPDATES;
       
   111 
       
   112         eval { open(my $x, "+>>", $dumpdates) or die "$!\n" };
       
   113         if   (chomp $@) { ERROR "dumpdates file \"$dumpdates\": $@" }
       
   114         else            { OK "dumpdates file: \"$dumpdates\"" }
       
   115     }
       
   116 
       
   117     exit 0;
       
   118 }
       
   119 
       
   120 sub exec_estimate {
       
   121 
       
   122     # $opt_level, $opt_device
       
   123     my @cmd = (
       
   124         dump => "-$opt_level",
       
   125         "-S",
       
   126         $opt_record && $opt_dumpdates ? (-D => expand($opt_dumpdates)) : (),
       
   127         device($opt_device),
       
   128     );
       
   129 
       
   130     chomp(my $output = `@cmd 2>&1`);
       
   131 
       
   132     if ($? or $output !~ /^\d+/) {
       
   133         say "unexpected output \"$output\"";
       
   134         exit 1;
       
   135     }
       
   136 
       
   137     $output /= 1024;    # to get 1K blocks
       
   138 
       
   139     # level blocks blocksize
       
   140     # --> the blocksize unit is K
       
   141     say "$opt_level $output 1";
       
   142     exit 0;
       
   143 }
       
   144 
       
   145 sub exec_backup {
       
   146 
       
   147     # fd1: data channel
       
   148     # fd3: message channel
       
   149     # fd4: index channel
       
   150 
       
   151     my @dump = (
       
   152         dump => "-$opt_level",
       
   153         -f   => "-",
       
   154         $opt_record ? "-u" : (),
       
   155         $opt_record && $opt_dumpdates ? (-D => expand($opt_dumpdates)) : (),
       
   156         device($opt_device)
       
   157     );
       
   158 
       
   159     # messages ----------,
       
   160     #   ,---------> fd2 ----> fd3
       
   161     # dump --o----> fd1                (data)
       
   162     #         `---> restore -t --> fd4 (index)
       
   163 
       
   164     open(my $msg, ">&=", FD3) or die "Can't open fd3: $!\n";
       
   165     open(my $idx, ">&=", FD4) or die "Can't open fd4: $!\n" if $opt_index;
       
   166 
       
   167     if ($opt_index) {
       
   168 	my $pid = fork // die "Can't fork: $!\n";
       
   169 	if (not $pid) {
       
   170 	    open(STDOUT, "|-") or do {
       
   171 		open(my $restore, "|-") or do {
       
   172 		    open(STDOUT, "|-") or do {
       
   173 			select($idx);
       
   174 			postprocess_toc();
       
   175 			exit 0;
       
   176 		    };
       
   177 		    exec "restore", "-tvf" => "-";
       
   178 		    die "Can't exec `restore -tvf -`: $!";
       
   179 		};
       
   180 		local $/ = 2**16;
       
   181 		while (<STDIN>) {
       
   182 		    print $_;
       
   183 		    print $restore $_;
       
   184 		};
       
   185 		exit 0;
       
   186 	    };
       
   187 
       
   188 	    open(STDERR, "|-") or do {
       
   189 		select($msg);
       
   190 		postprocess_dump_messages();
       
   191 		exit 0;
       
   192 	    };
       
   193 
       
   194 
       
   195 	    exec @dump;
       
   196 	    die "Can't exec `@dump`: $!\n";
       
   197 	}
       
   198 
       
   199 	waitpid($pid, 0);
       
   200 	exit $?;
       
   201     }
       
   202 
       
   203     # no need to send an index
       
   204     my $pid = fork // die "Can't fork: $!\n";
       
   205     if (not $pid) {
       
   206 	open(STDERR, "|-") or do {
       
   207 	    select($msg);
       
   208 	    postprocess_dump_messages();
       
   209 	    exit 0;
       
   210 	};
       
   211 	exec @dump;
       
   212 	die "Can't exec `@dump`: $!\n";
       
   213     }
       
   214     waitpid($pid, 0);
       
   215     exit $?;
       
   216 
       
   217 }
       
   218 
       
   219 sub postprocess_dump_messages() {
       
   220     while (<STDIN>) {
       
   221         print "| $_";
       
   222 
       
   223         if (/^\s+DUMP: (\d+) blocks?/) {
       
   224 
       
   225             # we assume a block size of 1K
       
   226             say "sendbackup: size $1";
       
   227         }
       
   228         elsif (/^\s+DUMP: DUMP IS DONE/) {
       
   229             say "sendbackup: end";
       
   230         }
       
   231     }
       
   232 }
       
   233 
       
   234 sub postprocess_toc {
       
   235     # dir  4711 ./aaa
       
   236     # leaf 4712 ./bbb/xxx
       
   237     # leaf 4713 ./bbb/a
       
   238     # b
       
   239     # leaf 8819 ./bbb/x
       
   240 
       
   241     my $name;
       
   242 
       
   243     while (<STDIN>) {
       
   244 	chomp;
       
   245 	if (/^(dir|leaf)\s+\d+\s+(\.\/.*)/) {
       
   246 	    say $name if defined $name;
       
   247 	    $name = $2 . ($1 eq "dir" ? "/" : "");
       
   248 	    next;
       
   249 	}
       
   250 
       
   251 	if ($name) {
       
   252 	    $name .= $_;
       
   253 	    next;
       
   254 	}
       
   255 	
       
   256     }
       
   257 
       
   258     say $name if defined $name;
       
   259 
       
   260 }
       
   261 
       
   262 sub device {
       
   263     my $_ = shift;
       
   264     return $_ if /^\//;
       
   265     return "/dev/$_";
       
   266 }
       
   267 
       
   268 sub expand {
       
   269     my $_ = shift;
       
   270     s/\${c}/$opt_config/g;
       
   271     return $_;
       
   272 }
       
   273 
       
   274 sub OK    { say "OK ",    @_ }
       
   275 sub ERROR { say "ERROR ", @_ }
       
   276 
       
   277 =head1 NAME
       
   278 
       
   279   amdumpext - the amanda dump application
       
   280 
       
   281 =head1 SYNOPSIS
       
   282 
       
   283   amdumpext support|selfcheck [options]
       
   284 
       
   285 =head1 COMMANDS
       
   286 
       
   287 =over
       
   288 
       
   289 =item B<support>
       
   290 
       
   291 Send a list of supported features.
       
   292 
       
   293 =back
       
   294 
       
   295 =head1 OPTIONS
       
   296 
       
   297 =head2 Common Options
       
   298 
       
   299 The following options have to be supported by the application.
       
   300 
       
   301 =over 4
       
   302 
       
   303 =item B<--config> I<config>
       
   304 
       
   305 The configuration to be used (the backup set).
       
   306 
       
   307 =item B<--host> I<host>
       
   308 
       
   309 The host from the DLE.
       
   310 
       
   311 =item B<--disk> I<disk>
       
   312 
       
   313 The disk to be saved. It's some "label" for the device to be backed up.
       
   314 
       
   315 =item B<--device> I<device>
       
   316 
       
   317 The device to be backed up (may be a device name, a mountpoint).
       
   318 
       
   319 =back
       
   320 
       
   321 =head2 Optional options
       
   322 
       
   323 The following options need to be supported if indicated by the "support"
       
   324 command.
       
   325 
       
   326 =over
       
   327 
       
   328 =item B<--message> "line"
       
   329 
       
   330 Send messages line by line.
       
   331 
       
   332 =item B<--index> "line"
       
   333 
       
   334 Send the index line by line.
       
   335 
       
   336 =back
       
   337 
       
   338 =cut
       
   339 
       
   340 =head2 Properties
       
   341 
       
   342 =over 4
       
   343 
       
   344 =item B<--dumpdates> I<dumpdates>
       
   345 
       
   346 The location of the dumpdates file. Placeholder "${c}" is allowed and
       
   347 replaced by the name of the current config.
       
   348 
       
   349 =back
       
   350 
       
   351 # vim:sts=4 sw=4 aw ai sm: