Begin work on a reference decoder for backups.
[cumulus.git] / restore.pl
1 #!/usr/bin/perl -w
2 #
3 # Proof-of-concept/reference decoder for LBS-format backup snapshots.
4 #
5 # This decoder aims to decompress an LBS snapshot.  It is not meant to be
6 # particularly efficient, but should be a small and portable tool for doing so
7 # (important for recovering from data loss).  It is also meant to serve as a
8 # check on the snapshot tool and data format itself, and serve as documentation
9 # for the format.
10 #
11 # This decoder does not understand TAR archives; it assumes that all segments
12 # in the snapshot have already been decompressed, and that objects are
13 # available simply as files in the filesystem.  This simplifies the design.
14 #
15 # Copyright (C) 2007  Michael Vrable
16
17 use strict;
18 use Digest::SHA1;
19
20 my $OBJECT_DIR = ".";           # Directory where objects are unpacked
21
22 ############################ CHECKSUM VERIFICATION ############################
23 # A very simple later for verifying checksums.  Checksums may be used on object
24 # references directly, and can also be used to verify entire reconstructed
25 # files.
26 #
27 # A checksum to verify is given in the form "algorithm=hexdigest".  Given such
28 # a string, we can construct a "verifier" object.  Bytes can be incrementally
29 # added to the verifier, and at the end a test can be made to see if the
30 # checksum matches.  The caller need not know what algorithm is used.  However,
31 # at the moment we only support SHA-1 for computing digest (algorith name
32 # "sha1").
33 sub verifier_create {
34     my $checksum = shift;
35
36     if ($checksum !~ m/^(\w+)=([0-9a-f]+)$/) {
37         die "Malformed checksum: $checksum";
38     }
39     my ($algorithm, $hash) = ($1, $2);
40     if ($algorithm ne 'sha1') {
41         die "Unsupported checksum algorithm: $algorithm";
42     }
43
44     my %verifier = (
45         ALGORITHM => $algorithm,
46         HASH => $hash,
47         DIGESTER => new Digest::SHA1
48     );
49
50     return \%verifier;
51 }
52
53 sub verifier_add_bytes {
54     my $verifier = shift;
55     my $digester = $verifier->{DIGESTER};
56     my $data = shift;
57
58     $digester->add($data);
59 }
60
61 sub verifier_check {
62     my $verifier = shift;
63     my $digester = $verifier->{DIGESTER};
64
65     my $newhash = $digester->hexdigest();
66     return ($verifier->{HASH} eq $newhash);
67 }
68
69 ################################ OBJECT ACCESS ################################
70 # The base of the decompressor is the object reference layer.  See ref.h for a
71 # description of the format for object references.  These functions will parse
72 # an object reference, locate the object data from the filesystem, perform any
73 # necessary integrity checks (if a checksum is included), and return the object
74 # data.
75 sub load_ref {
76     # First, try to parse the object reference string into constituent pieces.
77     # The format is segment/object(checksum)[range].  Both the checksum and
78     # range are optional.
79     my $ref_str = shift;
80
81     if ($ref_str !~ m/^([-0-9a-f]+)\/([0-9a-f]+)(\(\S+\))?(\[\S+\])?$/) {
82         die "Malformed object reference: $ref_str";
83     }
84
85     my ($segment, $object, $checksum, $range) = ($1, $2, $3, $4);
86
87     # Next, use the segment/object components to locate and read the object
88     # contents from disk.
89     open OBJECT, "<", "$OBJECT_DIR/$segment/$object"
90         or die "Unable to open object: $OBJECT_DIR/$segment/$object";
91     my $contents = join '', <OBJECT>;
92     close OBJECT;
93
94     # If a checksum was specified in the object reference, verify the object
95     # integrity by computing a checksum of the read data and comparing.
96     if ($checksum) {
97         $checksum =~ m/^\((\S+)\)$/;
98         my $verifier = verifier_create($1);
99         verifier_add_bytes($verifier, $contents);
100         if (!verifier_check($verifier)) {
101             die "Integrity check for object $ref_str failed";
102         }
103     }
104
105     # If a range was specified, then only a subset of the bytes of the object
106     # are desired.  Extract just the desired bytes.
107     if ($range) {
108         if ($range !~ m/^\[(\d+)\+(\d+)\]$/) {
109             die "Malformed object range: $range";
110         }
111
112         my $object_size = length $contents;
113         my ($start, $length) = ($1 + 0, $2 + 0);
114         if ($start >= $object_size || $start + $length > $object_size) {
115             die "Object range $range falls outside object bounds "
116                 . "(actual size $object_size)";
117         }
118
119         $contents = substr $contents, $start, $length;
120     }
121
122     return $contents;
123 }
124
125 ############################### MAIN ENTRY POINT ##############################
126 my $object = $ARGV[0];
127
128 #print "Object: $object\n\n";
129
130 my $contents = load_ref($object);
131 print $contents;