chiark / gitweb /
Strip redundant Emacs mode markers from Perl scripts.
[distorted-backup] / snap.rfreezefs.in
CommitLineData
99248ed2 1#! @PERL@
99248ed2
MW
2###
3### Synchronize snapshot with remotely mounted filesystem
4###
5### (c) 2011 Mark Wooding
6###
7
8###----- Licensing notice ---------------------------------------------------
9###
10### This program is free software; you can redistribute it and/or modify
11### it under the terms of the GNU General Public License as published by
12### the Free Software Foundation; either version 2 of the License, or
13### (at your option) any later version.
14###
15### This program is distributed in the hope that it will be useful,
16### but WITHOUT ANY WARRANTY; without even the implied warranty of
17### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18### GNU General Public License for more details.
19###
20### You should have received a copy of the GNU General Public License
21### along with this program; if not, write to the Free Software Foundation,
22### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24use Socket;
25
26###--------------------------------------------------------------------------
27### Utilities.
28
29(our $QUIS = $0) =~ s:^.*/::;
30sub whine ($) { my ($msg) = @_; print STDERR "$QUIS: $msg\n"; }
31sub fail ($) { my ($msg) = @_; whine $msg; exit $! || ($? >> 8) || 255; }
32
33our @CLEANUP = ();
34sub cleanup (&) { my ($func) = @_; unshift @CLEAUP, $func; }
35END { local $?; for my $func (@CLEANUP) { &$func } }
36
37sub gripelist ($@) {
38 my ($gripe, @things) = @_;
39 fail "$gripe: " . join(", ", @things) if @things;
40}
41
42###--------------------------------------------------------------------------
43### Parse command line.
44
45our $USAGE = "usage: $QUIS DEVICE [KEY=VALUE ...]";
46sub version { print "$QUIS, version 1.0.0\n"; }
47sub help {
48 print <<EOF;
49$USAGE
50
51Option keys:
52 dir=MOUNTPT Mount point of filesystem on remote host [required].
53 host=[USER@]NAME Name or address of remote host [required].
54 op=OPERATION `snap' to create snapshot, or `unsnap' to remove.
55 rfreezefs=PATH Location of `rfreezefs' program on remote host.
56 ssh=PATH Location of remote-shell program on local host.
57 subtype=TYPE Type of snapshot to create [required].
58
59Other option keys are passed to the underlying snapshot TYPE.
60EOF
61}
62@ARGV >= 1 or do { print STDERR $USAGE, "\n"; exit 1; };
63$ARGV[0] eq "-v" || $ARGV[0] eq "--version" and do { version; exit; };
64$ARGV[0] eq "-h" || $ARGV[0] eq "--help" and do { version; help; exit; };
65
66our $DEV = shift;
67our %OPT = ( dir => undef,
68 host => undef,
69 op => "snap",
70 rfreezefs => "rfreezefs",
71 ssh => "ssh",
72 subtype => undef );
73our @PASS = ();
74
75for my $i (@ARGV) {
76 $i =~ /^([^\s=]+)=(.*)$/ or fail "malformed option `$i'";
77 my ($k, $v) = ($1, $2);
78 if ($k =~ /^([^.]+)\.(.+)$/) {
79 if ($2 eq "rfreezefs") { $k = $1; }
80 }
81 if (exists $OPT{$k}) { $OPT{$k} = $v; }
82 else { push @PASS, $i; }
83}
84gripelist "missing arguments", grep { !defined $OPT{$_} } keys %OPT;
85
86(my $host = $OPT{host}) =~ s/^.*@//;
87my $addr = inet_aton $host or fail "failed to resolve `$OPT{host}'";
88
89###--------------------------------------------------------------------------
90### Remove a snapshot if requested.
91
92if ($OPT{op} eq "unsnap") {
93
94 ## This doesn't require negotiation with the remote end.
95 if ($OPT{unsnap}) {
96 exec "snap.$OPT{subtype}", $DEV, "op=unsnap", @PASS;
97 fail "exec snap.$OPT{subtype}: $!";
98 }
99
100} elsif ($OPT{op} ne "snap") {
101 fail "unknown operation `$OPT{op}'";
102}
103
104###--------------------------------------------------------------------------
105### Run `rfreezefs' on the remote host and collect information.
106
107(my $dir = $OPT{dir}) =~ s/\'/'\\''/g;
108open SSH, "-|", $OPT{ssh}, $OPT{host}, "$OPT{rfreezefs} -n '$dir'"
109 or fail "open(ssh): $!";
110cleanup { close SSH };
111
112our %INF = ( PORT => undef );
113our %TOK = ();
114our %RTOK = ();
115our $PORT = undef;
116
117while (<SSH>) {
118 my @f = split;
119 if ($f[0] eq "PORT") { $INF{$f[0]} = $f[1]; }
120 elsif ($f[1] eq "TOKEN") { $TOK{$f[1]} = $f[2]; $RTOK{$f[2]} = $f[1]; }
121 elsif ($f[0] eq "READY") { last; }
122}
123
124gripelist "missing information", grep { !defined $INF{$_} } keys %INF;
125gripelist "missing tokens",
126 grep { !exists $TOK{$_} } "FREEZE", "FROZEN", "THAW", "THAWED";
127
128###--------------------------------------------------------------------------
129### Create the snapshot.
130
131## Connect to the socket.
132socket SK, PF_INET, SOCK_STREAM, 0 or fail "socket: $!";
133cleanup { close SK };
134select SK; $| = 1;
135connect SK, sockaddr_in($INF{PORT}, $addr) or fail "connect: $!";
136
137## Communication with the server.
138sub rffscmd ($;$) {
139 my ($cmd, $rpl) = @_;
140 print SK $TOK{$cmd}, "\n" or fail "write <$cmd>: $!";
141 if ($rpl) {
142 chomp (my $line = <SK>);
143 if ($line ne $TOK{$rpl}) {
144 my $what = exists $RTOK{$line} ? "<$RTOK{$line}>" : "`$line'";
145 fail "unexpected response $what to <$cmd>";
146 }
147 }
148}
149
150## Freeze the remote filesystem.
151rffscmd(FREEZE, FROZEN);
152
153## Create the snapshot locally using the appropriate mechanism. This will
154## print the snapshot device name.
155my $rc = system "snap.$OPT{subtype}", $DEV, @PASS;
156$rc and fail "snap.$OPT{subtype} failed (rc = $rc)";
157
158## Discard the snapshot again if anything goes wrong.
159cleanup {
160 if ($?) {
161 my $rc = system "snap.$OPT{subtype}", $DEV, "unsnap", @PASS;
162 $rc and
163 whine "snap.$OPT{subtype} failed to unsnap (rc = $rc) " .
164 "while recovering";
165 }
166};
167
168## Thaw the remote filesystem.
169rffscmd(THAW, THAWED);
170
171###----- That's all, folks --------------------------------------------------
172
173exit 0;