3 ### Create and remove snapshots of block devices
5 ### (c) 2011 Mark Wooding
8 ###----- Licensing notice ---------------------------------------------------
10 ### This file is part of the distorted.org.uk backup suite.
12 ### distorted-backup is free software; you can redistribute it and/or modify
13 ### it under the terms of the GNU General Public License as published by
14 ### the Free Software Foundation; either version 2 of the License, or
15 ### (at your option) any later version.
17 ### distorted-backup is distributed in the hope that it will be useful,
18 ### but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ### GNU General Public License for more details.
22 ### You should have received a copy of the GNU General Public License along
23 ### with distorted-backup; if not, write to the Free Software Foundation,
24 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 use Getopt::Long qw(:config gnu_compat bundling no_ignore_case);
29 our $VERSION = "@VERSION@";
31 our %C = ( etc => "@sysconfdir@",
33 snap => "@snaplibexecdir@" );
35 ###--------------------------------------------------------------------------
38 (our $QUIS = $0) =~ s:^.*/::;
39 sub whine ($) { my ($msg) = @_; print STDERR "$QUIS: $msg\n"; }
40 sub fail ($) { my ($msg) = @_; whine $msg; exit $! || ($? >> 8) || 255; }
42 ###--------------------------------------------------------------------------
43 ### Parse command line.
45 our $USAGE = "usage: $QUIS [-u] [-c FILE] DEVICE [KEY=VALUE ...]";
46 sub version { print "$QUIS, version $VERSION\n"; }
52 -h, --help Show this help text.
53 -v, --version Show the program version number.
54 -c, --config=FILE Use configuration FILE, not $CONF.
55 -n, --no-act Don't actually do anything; show what would be done.
56 -u, --unsnap Remove a snapshot taken earlier.
60 our $CONF = "$C{etc}/snaptab";
63 GetOptions('help|h|?' => sub { version; help; exit; },
64 'version|v' => sub { version; exit; },
65 'config-file|c=s' => \$CONF,
66 'no-act|n' => \$NOACT,
67 'unsnap|u' => sub { $OP = "unsnap"; })
69 or do { print STDERR $USAGE, "\n"; exit 1; };
74 ###--------------------------------------------------------------------------
75 ### Parse the configuration file.
77 open CF, "<", $CONF or fail "open config ($CONF): $!";
80 while (my $line = <CF>) {
82 while ($line =~ /\\\s*$/) {
83 chomp (my $more = <CF>);
84 $line =~ s/\\\s*$/$more/;
86 next if $line =~ /^\s*(\#|$)/;
87 my ($dev, $type, @opts) = shellwords $line;
90 if ($i !~ /^\*\.(.+)$/) { push @nopts, $i; next; }
92 for my $o (@{$DEF{$ty}}) {
93 $o =~ /^([^=]+)=(.*)$/;
94 my ($k, $v) = ($1, $2);
95 ($k, $ty) = ($1, $2) if $k =~ /^(.+)\.([^.]+)/;
96 push @nopts, "$k.$ty=$v";
100 if ($dev eq "*") { push @{$DEF{$type}}, @opts; }
101 elsif ($dev eq $DEV) { push @KV, "type=$type", @{$DEF{$type}}, @opts; }
103 close CF or fail "close config ($CONF): $!";
105 ###--------------------------------------------------------------------------
106 ### Pick out the winning options.
111 for my $i (reverse @KV, "op=$OP", @ARGV) {
112 $i =~ /^([^=]+)=(.*)$/ or fail "malformed option `$i': missing `='";
113 my ($k, $v) = ($1, $2);
114 unless (exists $seen{$k}) {
116 if ($k eq "type") { $TYPE = $v; }
117 else { push @OPT, "$k=$v"; }
121 defined $TYPE or fail "no snapshot type for device `$DEV'";
124 ###--------------------------------------------------------------------------
125 ### Invoke the type-specific handler.
127 ## Fix up the path, to make sure our tools are available.
128 my $path = $ENV{PATH};
129 my %path = map { $_ => 1 } split /:/, $path;
130 for my $p (qw( /bin /sbin /usr/bin /usr/sbin ), $C{sbin}) {
131 $path = "$p:$path" unless exists $path{$p};
135 ## Prepare the arguments.
136 my @args = ("$C{snap}/snap.$TYPE", $DEV, @OPT);
140 whine "run " . join(" ",
142 grep { s/'/\\'/g; 1 }
146 fail "exec (snap.$TYPE): $!";
149 ###----- That's all, folks --------------------------------------------------