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