File Coverage

blib/lib/Brackup/StoredChunk.pm
Criterion Covered Total %
statement 92 100 92.0
branch 26 34 76.4
condition 7 9 77.7
subroutine 22 24 91.6
pod 0 18 0.0
total 147 185 79.4


line stmt bran cond sub pod time code
1             package Brackup::StoredChunk;
2              
3 13     13   75 use strict;
  13         33  
  13         2508  
4 13     13   83 use warnings;
  13         25  
  13         451  
5 13     13   67 use Carp qw(croak);
  13         26  
  13         1848  
6 13     13   609 use Brackup::Util qw(io_sha1);
  13         37  
  13         668  
7 13     13   74 use Fcntl qw(SEEK_SET);
  13         20  
  13         18007  
8              
9             # fields:
10             # pchunk - always
11             # backlength - memoized
12             # backdigest - memoized
13             # _chunkref - memoized
14             # compchunk - composite chunk, if we were added to a composite chunk
15             # compfrom - offset in composite chunk where we start
16             # compto - offset in composite chunk where we end
17             # lite - true if this is a 'lite' or 'handle' version
18              
19             sub new {
20 88     88 0 255 my ($class, $pchunk) = @_;
21 88         649 my $self = bless {}, $class;
22 88         1102 $self->{pchunk} = $pchunk;
23 88         307 return $self;
24             }
25              
26 75     75 0 422 sub pchunk { $_[0]{pchunk} }
27              
28             # create the 'lite' or 'handle' version of a storedchunk. can't get to
29             # the chunkref from this, but callers aren't won't. and we'll DIE if they
30             # try to access the chunkref.
31             sub new_from_inventory_value {
32 23     23 0 90 my ($class, $pchunk, $invval) = @_;
33              
34 23         149 my ($dig, $len, $range) = split /\s+/, $invval;
35              
36 23         233 my $sc = bless {
37             pchunk => $pchunk,
38             backdigest => $dig,
39             backlength => $len,
40             lite => 1,
41             }, $class;
42              
43             # normal
44 23 50       245 return $sc unless $range;
45              
46             # in case of little file in a composite chunk,
47             # we gotta be a range.
48 0 0       0 my ($from, $to) = $range =~ /^(\d+)-(\d+)$/
49             or die "bogus range: $range";
50 0         0 $sc->{compfrom} = $from;
51 0         0 $sc->{compto} = $to;
52 0         0 return $sc;
53             }
54              
55             sub clone_but_for_pchunk {
56 4     4 0 29 my ($self, $pchunk) = @_;
57 4         51 my $copy = bless {}, ref $self;
58 4         551 foreach my $f (qw(backlength backdigest compchunk compfrom compto)) {
59 20         79 $copy->{$f} = $self->{$f};
60             }
61 4         112 $copy->{pchunk} = $pchunk;
62 4         37 return $copy;
63             }
64              
65             sub set_composite_chunk {
66 11     11 0 30 my ($self, $cchunk, $from, $to) = @_;
67 11         35 $self->{compchunk} = $cchunk;
68              
69             # forget our backup length/digest. this handle information
70             # to the stored chunk should be asked of our composite
71             # chunk in the future, when it's done populating.
72 11         22 $self->{backdigest} = undef;
73 11         23 $self->{backlength} = undef;
74 11         86 $self->forget_chunkref;
75              
76 11         53 $self->{compfrom} = $from;
77 11         44 $self->{compto} = $to;
78             }
79              
80             sub range_in_composite {
81 193     193 0 415 my $self = shift;
82 193 100 100     6496 return undef unless $self->{compfrom} || $self->{compto};
83 30         187 return "$self->{compfrom}-$self->{compto}";
84             }
85              
86             sub file {
87 296     296 0 922 my $self = shift;
88 296         1831 return $self->{pchunk}->file;
89             }
90              
91             sub root {
92 255     255 0 850 my $self = shift;
93 255         838 return $self->file->root;
94             }
95              
96             # returns true if encrypted, false otherwise
97             sub encrypted {
98 255     255 0 1575 my $self = shift;
99 255 100       1412 return $self->root->gpg_rcpts ? 1 : 0;
100             }
101              
102             sub compressed {
103 0     0 0 0 my $self = shift;
104             # TODO/FUTURE: support compressed chunks (for non-encrypted
105             # content; gpg already compresses)
106 0         0 return 0;
107             }
108              
109             # the original length, pre-encryption
110             sub length {
111 0     0 0 0 my $self = shift;
112 0         0 return $self->{pchunk}->length;
113             }
114              
115             # the length, either encrypted or not
116             sub backup_length {
117 420     420 0 2012 my $self = shift;
118 420 100       5157 return $self->{backlength} if defined $self->{backlength};
119 48         222 $self->_populate_lengthdigest;
120 48         184 return $self->{backlength};
121             }
122              
123             # the digest, either encrypted or not
124             sub backup_digest {
125 270     270 0 752 my $self = shift;
126 270 100       1736 return $self->{backdigest} if $self->{backdigest};
127 22         68 $self->_populate_lengthdigest;
128 22         85 return $self->{backdigest};
129             }
130              
131             sub _populate_lengthdigest {
132 70     70   133 my $self = shift;
133              
134             # Composite chunk version
135 70 100       478 if (my $cchunk = $self->{compchunk}) {
136 15         65 $self->{backlength} = $cchunk->backup_length;
137 15         71 $self->{backdigest} = $cchunk->digest;
138 15         47 return 1;
139             }
140              
141 55 50       215 die "ASSERT: encrypted length or digest not set" if $self->encrypted;
142              
143             # Unencrypted version
144 55         311 $self->{backdigest} = "sha1:" . io_sha1($self->{pchunk}->raw_chunkref);
145 55         265 $self->{backlength} = $self->{pchunk}->length; # length of raw data
146 55         120 return 1;
147             }
148              
149             sub chunkref {
150 88     88 0 195 my $self = shift;
151 88 100       1242 if ($self->{_chunkref}) {
152 33         1531 $self->{_chunkref}->seek(0, SEEK_SET);
153 33         834 return $self->{_chunkref};
154             }
155              
156             # encrypting case: chunkref gets set via set_encrypted_chunkref in Backup::backup
157 55 50       167 croak "ASSERT: encrypted but no chunkref set" if $self->encrypted;
158              
159             # caller/consistency check:
160 55 50       339 Carp::confess("Can't access chunkref on lite StoredChunk instance (handle only)")
161             if $self->{lite};
162              
163             # non-encrypting case
164 55         236 return $self->{_chunkref} = $self->{pchunk}->raw_chunkref;
165             }
166              
167             # set encrypted chunk filehandle and digest/length
168             sub set_encrypted_chunkref {
169 33     33 0 266 my ($self, $fh, $enc_length) = @_;
170 33 50       581 die "ASSERT: not enc" unless $self->encrypted;
171 33 50 33     533 die "ASSERT: already set?" if $self->{backlength} || $self->{backdigest};
172              
173 33         644 $self->{backdigest} = "sha1:" . io_sha1($fh);
174 33         207 $self->{backlength} = $enc_length;
175              
176 33         490 return $self->{_chunkref} = $fh;
177             }
178              
179             # lose the chunkref data
180             sub forget_chunkref {
181 92     92 0 418 my $self = shift;
182 92 100       888 return unless $self->{_chunkref};
183 81         1160 $self->{_chunkref}->close;
184 81         4599 delete $self->{_chunkref}; # this also deletes the tempfile, if any
185             }
186              
187             # to the format used by the metafile
188             sub to_meta {
189 112     112 0 254 my $self = shift;
190 112         6921 my @parts = ($self->{pchunk}->offset,
191             $self->{pchunk}->length);
192              
193 112 100       415 if (my $range = $self->range_in_composite) {
194 19         50 push @parts, (
195             $range,
196             $self->backup_digest,
197             );
198             } else {
199 93         321 push @parts, (
200             $self->backup_length,
201             $self->backup_digest,
202             );
203             }
204              
205             # if the inventory database is lost, it should be possible to
206             # recover the inventory database from the *.brackup files.
207             # if a file only has on chunk, the digest(raw) -> digest(enc)
208             # can be inferred from the file's digest, then the stored
209             # chunk's digest. but if we have multiple chunks, we need
210             # to store each chunk's raw digest as well in the chunk
211             # list. we could do this all the time, but considering
212             # most files are small, we want to save space in the *.brackup
213             # meta file and only do it when necessary.
214 112 100 100     428 if ($self->encrypted && $self->file->chunks > 1) {
215 10         85 push @parts, $self->{pchunk}->raw_digest;
216             }
217              
218 112         2461 return join(";", @parts);
219             }
220              
221             # aka "instructions to attach to a pchunk, on how to recover the pchunk from a target"
222             sub inventory_value {
223 81     81 0 161 my $self = shift;
224              
225             # when this chunk was stored as part of a composite chunk, the instructions
226             # are of form:
227             # sha1:deadbeef 0-50
228             # which means download "sha1:deadbeef", then the contents will be in from
229             # byte offset 0 to byte offset 50 (length of 50).
230 81 100       367 if (my $range = $self->range_in_composite) {
231 11         49 return join(" ",
232             $self->backup_digest,
233             $self->backup_length,
234             $range);
235             }
236              
237             # else, the historical format:
238             # sha1:deadbeef
239 70         358 return join(" ", $self->backup_digest, $self->backup_length);
240             }
241              
242             1;