1 # Copyright (c) 2009 Zmanda, Inc. All Rights Reserved.
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License version 2 as published
5 # by the Free Software Foundation.
7 # This program is distributed in the hope that it will be useful, but
8 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 # You should have received a copy of the GNU General Public License along
13 # with this program; if not, write to the Free Software Foundation, Inc.,
14 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
19 package Amanda::Holding;
21 use base qw( Exporter );
25 use POSIX qw( :fcntl_h );
30 use Amanda::Config qw( :getconf );
31 use Amanda::Debug qw( debug );
38 Amanda::Holding -- interface to the holding disks
47 for my $hfile (Amanda::Holding::files()) {
48 my $hdr = Amanda::Holding::get_header($hfile);
50 $size_per_host{$hdr->{'name'}} += Amanda::Holding::file_size($hfile);
53 Schematic for something like C<amflush>:
55 for my $ts (sort Amanda::Holding::get_all_timestamps()) {
59 for my $hfile (Amanda::Holding::get_files_for_flush(@to_dump)) {
71 A holding disk is a directory given in a holdingdisk definition in
74 =item Holding directory
76 A holding directory is a subdirectory of a holding disk, generally named by
77 timestamp. Note, however, that this package does not interpret holding
78 directory names as timestamps, and does not provide direct access to holding
83 A holding file describes one or more os-level files (holding file chunks) in a
84 holding directory, together representing a single dump file.
88 A holding chunk is an individual os-level file representing part of a holding
89 file. Chunks are kept small to avoid hitting filesystem size ilmits, and are
90 linked together internally by filename.
96 /data/holding <-- holding disk
97 /data/holding/20070306123456 <-- holding directory
98 /data/holding/20070306123456/raj._video_a <-- holding file and chunk
99 /data/holding/20070306123456/raj._video_a.1 <-- holding chunk
103 Holding-disk files do not have a block size, so the size of the header is fixed
104 at 32k. Rather than hard-code that value, use the constant DISK_BLOCK_BYTES
109 Note that this package assumes that a config has been loaded (see
112 These three functions provide basic access to holding disks, files, and chunks:
118 returns an list of active disks, each represented as a string. This does not
119 return holding disks which are defined in C<amanda.conf> but not used.
123 returns a list of active holding files on all disks. Note that a dump may span
124 multiple disks, so there is no use in selecting files only on certain holding
127 =item C<file_chunks($file)>
129 returns a list of chunks for the given file. Chunk filenames are always fully
134 C<Amanda::Holding> provides a few utility functions on holding files. Note
135 that these functions require fully qualified pathnames.
139 =item C<file_size($file, $ignore_headers)>
141 returns the size of the holding file I<in kilobytes>, ignoring the size of the
142 headers if C<$ignore_headers> is true.
144 =item C<file_unlink($file)>
146 unlinks (deletes) all chunks comprising C<$file>, returning true on success.
148 =item C<get_header($file)>
150 reads and returns the header (see L<Amanda::Header>) for C<$file>.
154 The remaining two functions are utilities for amflush and related tools:
158 =item C<get_all_timestamps()>
160 returns a sorted list of all timestamps with dumps in any active holding disk.
162 =item C<get_files_for_flush(@timestamps)>
164 returns a sorted list of files matching any of the supplied timestamps. Files
165 for which no DLE exists in the disklist are ignored. If no timestamps are
166 provided, then all timestamps are considered.
172 use constant DISK_BLOCK_BYTES => 32768;
174 our @EXPORT_OK = qw(dirs files file_chunks
175 get_files_for_flush get_all_datestamps
176 file_size file_unlink get_header);
185 unless (my ($year, $month, $day, $hour, $min, $sec) =
186 ($str =~ /(\d{4})(\d{2})(\d{2})(?:(\d{2})(\d{2})(\d{2}))/));
188 return 0 if ($year < 1990 || $year > 2999);
189 return 0 if ($month < 1 || $month > 12);
190 return 0 if ($day < 1 || $day > 31);
192 return 0 if (defined $hour and $hour > 23);
193 return 0 if (defined $min and $min > 60);
194 return 0 if (defined $sec and $sec > 60);
202 # walk disks, directories, and files with nested loops
203 for my $disk (disks()) {
204 my $diskh = IO::Dir->new($disk);
205 next unless defined $diskh;
207 while (defined(my $datestr = $diskh->read())) {
208 next unless (_is_datestr($datestr));
210 my $dirh = IO::Dir->new(File::Spec->catfile($disk, $datestr));
211 while (defined(my $dirent = $dirh->read)) {
212 next if $dirent eq '.' or $dirent eq '..';
214 my $filename = File::Spec->catfile($disk, $datestr, $dirent);
215 next unless -f $filename;
217 my $hdr = get_header($filename);
218 next unless defined($hdr);
220 # ignore chunks and anything bogus
221 next if ($hdr->{'type'} != $Amanda::Header::F_DUMPFILE);
223 $file_fn->($filename, $hdr);
235 for my $hdname (@{getconf($CNF_HOLDINGDISK)}) {
236 my $cfg = lookup_holdingdisk($hdname);
237 next unless defined $cfg;
239 my $dir = holdingdisk_getconf($cfg, $HOLDING_DISKDIR);
240 next unless defined $dir;
251 my $each_file_fn = sub {
252 my ($filename, $header) = @_;
253 push @results, $filename;
255 _walk($each_file_fn);
265 last unless -f $filename;
266 my $hdr = get_header($filename);
267 last unless defined($hdr);
269 push @results, $filename;
271 if ($hdr->{'cont_filename'}) {
272 $filename = $hdr->{'cont_filename'};
274 # no continuation -> we're done
284 return unless -f $filename;
286 my $fd = POSIX::open($filename, O_RDONLY);
289 my $hdr_bytes = Amanda::Util::full_read($fd, DISK_BLOCK_BYTES);
291 if (length($hdr_bytes) < DISK_BLOCK_BYTES) {
295 return Amanda::Header->from_string($hdr_bytes);
301 for my $chunk (file_chunks($filename)) {
302 unlink($chunk) or return 0;
309 my ($filename, $ignore_headers) = @_;
310 my $total = Math::BigInt->new(0);
312 for my $chunk (file_chunks($filename)) {
313 my $sb = stat($chunk);
314 my $size = Math::BigInt->new($sb->size);
315 $size -= DISK_BLOCK_BYTES if $ignore_headers;
316 $size = ($size + 1023) / 1024;
324 sub get_files_for_flush {
328 my $each_file_fn = sub {
329 my ($filename, $header) = @_;
330 if (@dateargs && !grep { $_ eq $header->{'datestamp'}; } @dateargs) {
334 if (!Amanda::Disklist::get_disk($header->{'name'}, $header->{'disk'})) {
338 push @results, $filename;
340 _walk($each_file_fn);
342 return sort @results;
345 sub get_all_datestamps {
348 my $each_file_fn = sub {
349 my ($filename, $header) = @_;
350 $datestamps{$header->{'datestamp'}} = 1;
352 _walk($each_file_fn);
354 return sort keys %datestamps;