diff options
author | Arne Jansen <sensille@gmx.net> | 2012-11-01 15:39:57 +0100 |
---|---|---|
committer | Arne Jansen <sensille@gmx.net> | 2012-11-01 15:39:57 +0100 |
commit | 5d62909acf93df2b6cc9323c4c7c2bbb6e77feb8 (patch) | |
tree | cb4914341cad0bc7e8c12be1233b597fd6faca43 | |
parent | ae12daee820f22f8e095ef72747e098f28390e47 (diff) | |
parent | 574be62b318a10613c646cf44fc750d5d4b7355a (diff) | |
download | far-progs-5d62909acf93df2b6cc9323c4c7c2bbb6e77feb8.tar.gz |
Merge remote-tracking branch 'janosch/master'
61 files changed, 1562 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d90ee5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +actions +fardump +fssum @@ -5,7 +5,7 @@ else CFLAGS = -D__SOLARIS__ -g endif -all: fardump fssum +all: fardump fssum actions fardump: fardump.c gcc -Wall fardump.c -o fardump @@ -13,5 +13,8 @@ fardump: fardump.c fssum: fssum.c gcc -Wall $(CFLAGS) fssum.c -o fssum -lssl -lcrypto +actions: meta/*.mac + perl expand.pl -o actions -q meta/*.mac + clean: - rm -rf fardump fssum *.o + rm -rf actions fardump fssum *.o diff --git a/expand.pl b/expand.pl new file mode 100755 index 0000000..2e66087 --- /dev/null +++ b/expand.pl @@ -0,0 +1,127 @@ +#!/usr/bin/perl + +# Copyright (C) 2012 STRATO. All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public +# License v2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 021110-1307, USA. + +use strict; +use warnings; +use Getopt::Std; + +sub permute ($@) { + my $res = shift; + my @idx = 0..$#_; + while (1) { + push @$res, [@_[@idx]]; + my $p = $#idx; + --$p while $idx[$p-1] > $idx[$p]; + my $q = $p or return; + push @idx, reverse splice @idx, $p; + ++$q while $idx[$p-1] > $idx[$q]; + @idx[$p-1,$q]=@idx[$q,$p-1]; + } +} + +sub enum($$$$$); +sub output($$$); +sub enum($$$$$) { + my ($fn, $lines, $names, $ix, $out) = @_; + + if ($ix == @$names) { + output($fn, $lines, $out); + return; + } + + my $a = $names->[$ix]; + my ($n, $e) = @$a; + my $res = []; + permute($res, keys %$e); + + foreach my $r (@$res) { + my %o; + my $i = 0; + foreach (@$r) { + $o{$n}->{$_} = ++$i; + } + enum($fn, $lines, $names, $ix + 1, { %$out, %o}); + } +} + +my $outdir; +my $cnt; +my %opts; +sub output($$$) { + my ($fn, $lines, $out) = @_; + my $repl = sub { + my $r = shift; + if ($r =~ /^(.*)\.(.*)$/) { + return "$1.".$out->{$1}->{$2}; + } else { + return $out->{"INO"}->{$1}; + } + }; + + $fn =~ m{(?:^|/)(\d+)-(.*).mac$}; + die "filename not of the form <digits>-<text>.mac" unless defined $2; + print "writing $1:$cnt-$2.ac\n" if !$opts{q}; + my $path = "$outdir/$1:$cnt-$2.ac"; + open FH, ">$path" or die "failed to write $path: $!\n"; + foreach my $line (@$lines) { + my $l = $line; + $l =~ s/\$([\d\w.]+)/$repl->($1)/ge; + print FH $l; + } + close FH; + ++$cnt; +} + +my $all_ok = getopts("ho:q", \%opts); + +if (!$all_ok || $opts{h} || !$opts{o} || !@ARGV) { + print STDERR "usage: $0 [-q] -o outdir filename...\n"; + exit(!$opts{h}); +} + +$outdir = $opts{o}; +mkdir($outdir); + +foreach my $fn (@ARGV) { + print "reading $fn\n" if !$opts{q}; + open FH, "<$fn" or die; + my @lines; + my %names; + $cnt = "001"; + while(<FH>) { + my $l; + $l = $_; + while($l =~ s/^[^\$]*\$([\d\w.]+)//) { + my $var = $1; + if ($var =~ /^(.*)\.(.*)$/) { + $names{$1}->{$2} = 1; + } else { + $names{"INO"}->{$1} = 1; + } + } + push @lines, $_; + } + close FH; + + my @names; + foreach (keys %names) { + push @names, [$_, $names{$_}]; + } + + enum($fn, \@lines, \@names, 0, {}); +} diff --git a/meta/001-file-create.mac b/meta/001-file-create.mac new file mode 100644 index 0000000..b4371b1 --- /dev/null +++ b/meta/001-file-create.mac @@ -0,0 +1,6 @@ +# create a file in a dir +mkdir $1 /1 +snapshot + +mkfile $2 /1/2 +snapshot diff --git a/meta/002-rename.mac b/meta/002-rename.mac new file mode 100644 index 0000000..af515f6 --- /dev/null +++ b/meta/002-rename.mac @@ -0,0 +1,7 @@ +# rename +mkdir $1 /1 +mkfile $2 /1/$a.1 +snapshot + +rename /1/$a.1 /1/$a.2 +snapshot diff --git a/meta/003-link-rename.mac b/meta/003-link-rename.mac new file mode 100644 index 0000000..0bf3d51 --- /dev/null +++ b/meta/003-link-rename.mac @@ -0,0 +1,13 @@ +# rename multiple links +mkdir $1 /1 +mkdir $2 /2 +mkdir $3 /3 +mkfile $4 /1/4 +link /1/4 /2/$a.1 +snapshot + +link /1/4 /2/$a.2 +link /1/4 /3/4 +unlink /2/$a.1 +unlink /1/4 +snapshot diff --git a/meta/004-empty.mac b/meta/004-empty.mac new file mode 100644 index 0000000..d9a6a1b --- /dev/null +++ b/meta/004-empty.mac @@ -0,0 +1,3 @@ +# emtpy base snapshot and incremental snapshot +snapshot +snapshot diff --git a/meta/005-chmod.mac b/meta/005-chmod.mac new file mode 100644 index 0000000..3eda61b --- /dev/null +++ b/meta/005-chmod.mac @@ -0,0 +1,11 @@ +# chmod a file and a directory + +mkdir $1 /1 +mkfile $2 /1/2 + +snapshot + +chmod /1/2 0012 +chmod /1 0705 + +snapshot diff --git a/meta/006-chown.mac b/meta/006-chown.mac new file mode 100644 index 0000000..50b6e11 --- /dev/null +++ b/meta/006-chown.mac @@ -0,0 +1,10 @@ +# chown file and dir +mkdir $1 /1 +mkfile $2 /1/2 + +snapshot + +chown /1 2000:2001 +chown /1/2 3000:3001 + +snapshot diff --git a/meta/007-mtime.mac b/meta/007-mtime.mac new file mode 100644 index 0000000..fe30037 --- /dev/null +++ b/meta/007-mtime.mac @@ -0,0 +1,10 @@ +# mtime update file and dir +mkdir $1 /1 +mkfile $2 /1/2 + +snapshot + +mtime /1 +mtime /1/2 + +snapshot diff --git a/meta/008-atime.mac b/meta/008-atime.mac new file mode 100644 index 0000000..1c66a7d --- /dev/null +++ b/meta/008-atime.mac @@ -0,0 +1,10 @@ +# atime update file and dir +mkdir $1 /1 +mkfile $2 /1/2 + +snapshot + +atime /1 +atime /1/2 + +snapshot diff --git a/meta/009-truncate.mac b/meta/009-truncate.mac new file mode 100644 index 0000000..178ddc4 --- /dev/null +++ b/meta/009-truncate.mac @@ -0,0 +1,8 @@ +# file truncate +mkdir $1 /1 +mkfile $2 /1/2 654321 + +snapshot + +truncate /1/2 321098 +snapshot diff --git a/meta/010-symlink.mac b/meta/010-symlink.mac new file mode 100644 index 0000000..ba319a2 --- /dev/null +++ b/meta/010-symlink.mac @@ -0,0 +1,15 @@ +# add a symlink to a file and change it to something invalid in the next step +mkdir $1 /1 +mkfile $2 /1/2 +snapshot + +mksymlink $3 /1/3 2 +snapshot + +unlink /1/3 +remount +mksymlink $3 /1/3 ../notthere +snapshot + +chown /1/3 2000:2001 +snapshot diff --git a/meta/011-fifo.mac b/meta/011-fifo.mac new file mode 100644 index 0000000..77a3a87 --- /dev/null +++ b/meta/011-fifo.mac @@ -0,0 +1,18 @@ +# make a fifo and a file and replace them crosswise +mkdir $1 /1 +mkfifo $2 /1/2 +mkfile $3 /1/3 +snapshot + +unlink /1/2 +unlink /1/3 +snapshot +remount + +mkfile $2 /1/2 +mkfifo $3 /1/3 + +snapshot + +chown /1/3 2000:2001 +snapshot diff --git a/meta/012-mksparse.mac b/meta/012-mksparse.mac new file mode 100644 index 0000000..b9b9773 --- /dev/null +++ b/meta/012-mksparse.mac @@ -0,0 +1,6 @@ +# make two sparse files +mkdir 1 /1 +mksparse 2 /1/2 +mksparse 3 /1/3 12M + +snapshot diff --git a/meta/013-sparse-truncate.mac b/meta/013-sparse-truncate.mac new file mode 100644 index 0000000..6cbb5dc --- /dev/null +++ b/meta/013-sparse-truncate.mac @@ -0,0 +1,11 @@ +# make a sparse file and write to it +mkdir $2 /2 +mkfile $1 /2/1 4k +write /2/1 256k 4k +snapshot + +write /2/1 0 2k +snapshot + +truncate /2/1 32k +snapshot diff --git a/meta/014-same-name.mac b/meta/014-same-name.mac new file mode 100644 index 0000000..0537e2f --- /dev/null +++ b/meta/014-same-name.mac @@ -0,0 +1,7 @@ +# replace one file by another under the same name +mkdir $1 /1 +mkfile $2 /1/2 +snapshot + +mkfile $3 /1/2 +snapshot diff --git a/meta/015-new-dir.mac b/meta/015-new-dir.mac new file mode 100644 index 0000000..789a333 --- /dev/null +++ b/meta/015-new-dir.mac @@ -0,0 +1,8 @@ +# add a link in a new dir +mkdir $1 /1 +mkfile $2 /1/$a.1 +snapshot + +mkdir $3 /1/$a.2 +link /1/$a.1 /1/$a.2/2 +snapshot diff --git a/meta/016-mv-dir.mac b/meta/016-mv-dir.mac new file mode 100644 index 0000000..e66bd4f --- /dev/null +++ b/meta/016-mv-dir.mac @@ -0,0 +1,11 @@ +# move a dir into a new dir +mkdir $5 /5 +mkdir $3 /5/$a.1 +mkfile $2 /5/$a.1/2 +snapshot + +mkdir $1 /5/$a.2 +mkfile $4 /5/$a.1/4 +rename /5/$a.1 /5/$a.2/3 +unlink /5/$a.2/3/2 +snapshot diff --git a/meta/017-cross.mac b/meta/017-cross.mac new file mode 100644 index 0000000..64edc97 --- /dev/null +++ b/meta/017-cross.mac @@ -0,0 +1,11 @@ +# cross-link 2 files +mkdir $1 /1 +mkdir $4 /4 +mkfile $2 /1/2 +mkfile $3 /4/3 +snapshot + +rename /1/2 /4/3.1 +rename /4/3 /1/2 +rename /4/3.1 /4/3 +snapshot diff --git a/meta/018-two-links.mac b/meta/018-two-links.mac new file mode 100644 index 0000000..04669db --- /dev/null +++ b/meta/018-two-links.mac @@ -0,0 +1,7 @@ +# file with 2 links +mkdir $1 /1 +snapshot + +mkfile $2 /1/$a.1 +link /1/$a.1 /1/$a.2 +snapshot diff --git a/meta/019-rename-dir.mac b/meta/019-rename-dir.mac new file mode 100644 index 0000000..e038e1e --- /dev/null +++ b/meta/019-rename-dir.mac @@ -0,0 +1,9 @@ +# rename a dir and a file in a hierarchie +mkdir $1 /1 +mkdir $2 /1/$a.1 +mkfile $3 /1/$a.1/$b.1 +snapshot + +rename /1/$a.1 /1/$a.2 +rename /1/$a.2/$b.1 /1/$a.2/$b.2 +snapshot diff --git a/meta/020-tempname.mac b/meta/020-tempname.mac new file mode 100644 index 0000000..a981f39 --- /dev/null +++ b/meta/020-tempname.mac @@ -0,0 +1,11 @@ +# see if name resolution works when a tempname is involved, for pass 1 +mkdir $2 /2 +mkdir $5 /5 +mkfile $1 /2/1 +mkdir $3 /5/3 +snapshot + +mkfile $4 /5/3/4 +unlink /2/1 +rename /5/3 /2/1 +snapshot diff --git a/meta/021-file-to-dir.mac b/meta/021-file-to-dir.mac new file mode 100644 index 0000000..c51c9b3 --- /dev/null +++ b/meta/021-file-to-dir.mac @@ -0,0 +1,12 @@ +# file is replaced by a directory on the same inode number +mkdir $1 /1 +mkfile $2 /1/$a.1 +snapshot + +unlink /1/$a.1 +remount + + +mkdir $2 /1/$a.2 +mkfile $3 /1/$a.2/3 +snapshot diff --git a/meta/022-dir-to-file.mac b/meta/022-dir-to-file.mac new file mode 100644 index 0000000..f8f068e --- /dev/null +++ b/meta/022-dir-to-file.mac @@ -0,0 +1,13 @@ +# directory is replaced by a file on the same inode number +mkdir $1 /1 +mkdir $2 /1/$a.1 +mkfile $3 /1/$a.1/3 +snapshot + +unlink /1/$a.1/3 +rmdir /1/$a.1 +remount + + +mkfile $2 /1/$a.2 +snapshot diff --git a/meta/023-dir-name-exchange.mac b/meta/023-dir-name-exchange.mac new file mode 100644 index 0000000..1b0ce60 --- /dev/null +++ b/meta/023-dir-name-exchange.mac @@ -0,0 +1,11 @@ +# two dirs exchange names +mkdir $1 /1 +mkdir $2 /1/$a.1 +mkdir $3 /1/$a.2 +mkfile $4 /1/$a.2/4 +snapshot + +rename /1/$a.1 /1/2.1 +rename /1/$a.2 /1/$a.1 +rename /1/2.1 /1/$a.2 +snapshot diff --git a/meta/024-dir-tempname.mac b/meta/024-dir-tempname.mac new file mode 100644 index 0000000..87345e4 --- /dev/null +++ b/meta/024-dir-tempname.mac @@ -0,0 +1,11 @@ +# replace a directory by a file with tempname +mkdir $2 /2 +mkdir $3 /2/3 +mkfile $4 /2/3/4 +snapshot + +mkfile $1 /2/3.1 +unlink /2/3/4 +rmdir /2/3 +rename /2/3.1 /2/3 +snapshot diff --git a/meta/025-dir-tempname.mac b/meta/025-dir-tempname.mac new file mode 100644 index 0000000..bee8b18 --- /dev/null +++ b/meta/025-dir-tempname.mac @@ -0,0 +1,11 @@ +# replace a directory by a file with tempname +mkdir $2 /2 +mkdir $1 /2/1 +mkdir $4 /2/1/4 +snapshot + +mkfile $3 /2/1.1 +rmdir /2/1/4 +rmdir /2/1 +rename /2/1.1 /2/1 +snapshot diff --git a/meta/026-mknod.mac b/meta/026-mknod.mac new file mode 100644 index 0000000..b766d83 --- /dev/null +++ b/meta/026-mknod.mac @@ -0,0 +1,9 @@ +# mknod +mkdir $1 /1 +mknod $2 /1/$a.1 b 100 200 +mknod $3 /1/$a.2 c 101 201 +snapshot + +unlink /1/$a.1 +unlink /1/$a.2 +snapshot diff --git a/meta/027-rm-dir-delayed-rename.mac b/meta/027-rm-dir-delayed-rename.mac new file mode 100644 index 0000000..c0257a5 --- /dev/null +++ b/meta/027-rm-dir-delayed-rename.mac @@ -0,0 +1,12 @@ +# do a delayed rmdir and a rename in between +mkdir $2 /2 +mkdir $1 /2/$a.1 +mkfile $3 /2/$a.1/3 +snapshot + +unlink /2/$a.1/3 +rmdir /2/$a.1 +remount + +mkdir $1 /2/$a.2 +snapshot diff --git a/meta/028-replace-file.mac b/meta/028-replace-file.mac new file mode 100644 index 0000000..a31378f --- /dev/null +++ b/meta/028-replace-file.mac @@ -0,0 +1,10 @@ +# replace a file by a new file +mkdir $2 /2 +mkfile $1 /2/$a.1 +snapshot + +unlink /2/$a.1 +remount + +mkfile $1 /2/$a.2 +snapshot diff --git a/meta/029-dir-to-file.mac b/meta/029-dir-to-file.mac new file mode 100644 index 0000000..3f18bec --- /dev/null +++ b/meta/029-dir-to-file.mac @@ -0,0 +1,10 @@ +# dir is replaced by a file on the same inode number +mkdir $1 /1 +mkdir $2 /1/$a.1 +snapshot + +rmdir /1/$a.1 +remount + +mkfile $2 /1/$a.2 +snapshot diff --git a/meta/030-three-links.mac b/meta/030-three-links.mac new file mode 100644 index 0000000..74a011d --- /dev/null +++ b/meta/030-three-links.mac @@ -0,0 +1,13 @@ +# three links to a file from three different directories +mkdir $1 /1 +mkdir $2 /1/$a.1 +mkdir $3 /1/$a.2 +mkfile $4 /1/$a.4 +link /1/$a.4 /1/$a.1/$b.1 +link /1/$a.4 /1/$a.2/$b.1 +snapshot + +link /1/$a.4 /1/$b.2 +link /1/$a.1/$b.1 /1/$a.1/$b.2 +link /1/$a.2/$b.1 /1/$a.2/$b.2 +snapshot diff --git a/meta/031-replace-dir.mac b/meta/031-replace-dir.mac new file mode 100644 index 0000000..f4dbe63 --- /dev/null +++ b/meta/031-replace-dir.mac @@ -0,0 +1,12 @@ +# do a delayed rmdir and a rename in between +mkdir $2 /2 +mkdir $1 /2/$a.1 +mkdir $3 /2/$a.1/3 +snapshot + +rmdir /2/$a.1/3 +rmdir /2/$a.1 +remount + +mkdir $1 /2/$a.2 +snapshot diff --git a/meta/032-replace-dir.mac b/meta/032-replace-dir.mac new file mode 100644 index 0000000..82bb994 --- /dev/null +++ b/meta/032-replace-dir.mac @@ -0,0 +1,13 @@ +# replace a dir in the middle of the path +mkdir $1 /1 +mkdir $2 /1/$a.1 +mkdir $3 /1/$a.1/3 +snapshot + +rename /1/$a.1/3 /1/3 +rmdir /1/$a.1 +remount + +mkdir $2 /1/$a.2 +rename /1/3 /1/$a.2/3 +snapshot diff --git a/meta/033-replace-file.mac b/meta/033-replace-file.mac new file mode 100644 index 0000000..1b56c28 --- /dev/null +++ b/meta/033-replace-file.mac @@ -0,0 +1,10 @@ +# replace a file by a file on the same ino under a different name +mkdir $1 /1 +mkfile $2 /1/$a.1 +snapshot + +unlink /1/$a.1 +remount + +mkfile $2 /1/$a.2 +snapshot diff --git a/meta/034-replace-dir-same-name.mac b/meta/034-replace-dir-same-name.mac new file mode 100644 index 0000000..daacd10 --- /dev/null +++ b/meta/034-replace-dir-same-name.mac @@ -0,0 +1,13 @@ +# replace a dir with a dir on the same inode under the same name +mkdir $1 /1 +mkdir $2 /1/2 +mkfile $3 /1/2/3 +snapshot + +rename /1/2/3 /1/3 +rmdir /1/2 +remount + +mkdir $2 /1/2 +rename /1/3 /1/2/3 +snapshot diff --git a/meta/035-replace-file-cdev.mac b/meta/035-replace-file-cdev.mac new file mode 100644 index 0000000..c0f6e7b --- /dev/null +++ b/meta/035-replace-file-cdev.mac @@ -0,0 +1,10 @@ +# replace a file with a cdev on the same name and inode +mkdir $1 /1 +mkfile $2 /1/2 +snapshot + +unlink /1/2 +remount + +mknod $2 /1/2 c 100 101 +snapshot diff --git a/meta/036-replace-file-cdev.mac b/meta/036-replace-file-cdev.mac new file mode 100644 index 0000000..aea30a9 --- /dev/null +++ b/meta/036-replace-file-cdev.mac @@ -0,0 +1,8 @@ +# replace a file with a cdev on the same name and but different inode +mkdir $1 /1 +mkfile $2 /1/2 +snapshot + +unlink /1/2 +mknod $3 /1/2 c 100 101 +snapshot diff --git a/meta/037-replace-file-cdev.mac b/meta/037-replace-file-cdev.mac new file mode 100644 index 0000000..ec8551b --- /dev/null +++ b/meta/037-replace-file-cdev.mac @@ -0,0 +1,10 @@ +# replace a file with a cdev on the same inode but different name +mkdir $1 /1 +mkfile $2 /1/$a.1 +snapshot + +unlink /1/$a.1 +remount + +mknod $2 /1/$a.2 c 100 101 +snapshot diff --git a/meta/038-replace-mult-links.mac b/meta/038-replace-mult-links.mac new file mode 100644 index 0000000..e173ffe --- /dev/null +++ b/meta/038-replace-mult-links.mac @@ -0,0 +1,13 @@ +# replace a file on same ino, old has 1 link, new has 2 links +mkdir $2 /2 +mkdir $1 /2/$a.1 +mkfile $3 /2/$a.1/$b.1 +snapshot + +unlink /2/$a.1/$b.1 +remount + +mknod $3 /2/$a.1/$b.2 c 100 101 +mkdir $4 /2/$a.2 +link /2/$a.1/$b.2 /2/$a.2/$b.1 +snapshot diff --git a/meta/039-no-zfs-parent.mac b/meta/039-no-zfs-parent.mac new file mode 100644 index 0000000..cb3e736 --- /dev/null +++ b/meta/039-no-zfs-parent.mac @@ -0,0 +1,8 @@ +# no zfs parent set (patch required) +mkdir 1 /1 +mkdir 2 /1/2 +mkfile 3 /1/3 +link /1/3 /1/2/3 # 3's parent is now 2 +unlink /1/2/3 +rmdir /1/2 +snapshot diff --git a/meta/040-replace-mult-links.mac b/meta/040-replace-mult-links.mac new file mode 100644 index 0000000..2e1825b --- /dev/null +++ b/meta/040-replace-mult-links.mac @@ -0,0 +1,14 @@ +# replace a file on same ino, old has 2 link, new has 1 links +mkdir $2 /2 +mkdir $1 /2/$a.1 +mkdir $4 /2/$a.2 +mkfile $3 /2/$a.1/$b.1 +link /2/$a.1/$b.1 /2/$a.2/$b.1 +snapshot + +unlink /2/$a.1/$b.1 +unlink /2/$a.2/$b.1 +remount + +mknod $3 /2/$a.1/$b.2 c 100 101 +snapshot diff --git a/meta/041-replace-mult-links.mac b/meta/041-replace-mult-links.mac new file mode 100644 index 0000000..c2c238c --- /dev/null +++ b/meta/041-replace-mult-links.mac @@ -0,0 +1,17 @@ +# replace on same inode with multiple links +mkdir $3 /3 +mkdir $1 /3/1 +mkdir $2 /3/2 +mkdir $5 /3/5 +mkdir $6 /3/6 +mkfile $4 /3/1/4 +link /3/1/4 /3/2/4 +snapshot + +unlink /3/2/4 +unlink /3/1/4 +remount + +mknod $4 /3/5/4 c 100 101 +link /3/5/4 /3/6/4 +snapshot diff --git a/meta/042-replace-mult-links.mac b/meta/042-replace-mult-links.mac new file mode 100644 index 0000000..66d9353 --- /dev/null +++ b/meta/042-replace-mult-links.mac @@ -0,0 +1,13 @@ +# replace a file on same ino with the same name, old has 1 link, new has 2 links +mkdir $2 /2 +mkdir $1 /2/$a.1 +mkfile $3 /2/$a.1/3 +snapshot + +unlink /2/$a.1/3 +remount + +mknod $3 /2/$a.1/3 c 100 101 +mkdir $4 /2/$a.2 +link /2/$a.1/3 /2/$a.2/3 +snapshot diff --git a/meta/043-replace-mult-links.mac b/meta/043-replace-mult-links.mac new file mode 100644 index 0000000..382f507 --- /dev/null +++ b/meta/043-replace-mult-links.mac @@ -0,0 +1,14 @@ +# replace a file on same ino with the same name, old has 2 link, new has 1 links +mkdir $2 /2 +mkdir $1 /2/$a.1 +mkdir $4 /2/$a.2 +mkfile $3 /2/$a.1/3 +link /2/$a.1/3 /2/$a.2/3 +snapshot + +unlink /2/$a.1/3 +unlink /2/$a.2/3 +remount + +mknod $3 /2/$a.1/3 c 100 101 +snapshot diff --git a/meta/044-replace-mult-links.mac b/meta/044-replace-mult-links.mac new file mode 100644 index 0000000..c65bc49 --- /dev/null +++ b/meta/044-replace-mult-links.mac @@ -0,0 +1,18 @@ +# replace on same inode with partly the same name, with multiple links +mkdir $3 /3 +mkdir $1 /3/1 +mkdir $2 /3/2 +mkdir $5 /3/5 +mkdir $6 /3/6 +mkfile $4 /3/1/4 +link /3/1/4 /3/2/4 +snapshot + +unlink /3/2/4 +unlink /3/1/4 +remount + +mknod $4 /3/5/4 c 100 101 +link /3/5/4 /3/6/4 +link /3/5/4 /3/2/4 +snapshot diff --git a/meta/045-replace-mult-links.mac b/meta/045-replace-mult-links.mac new file mode 100644 index 0000000..4217bc2 --- /dev/null +++ b/meta/045-replace-mult-links.mac @@ -0,0 +1,15 @@ +# replace on same inode with the same name, with multiple links +mkdir $3 /3 +mkdir $1 /3/$a.1 +mkdir $2 /3/$a.2 +mkfile $4 /3/$a.1/4 +link /3/$a.1/4 /3/$a.2/4 +snapshot + +unlink /3/$a.2/4 +unlink /3/$a.1/4 +remount + +mknod $4 /3/$a.1/4 c 100 101 +link /3/$a.1/4 /3/$a.2/4 +snapshot diff --git a/meta/046-replace-file.mac b/meta/046-replace-file.mac new file mode 100644 index 0000000..3117058 --- /dev/null +++ b/meta/046-replace-file.mac @@ -0,0 +1,10 @@ +# replace a file by a file on the same ino under the same name +mkdir $1 /1 +mkfile $2 /1/2 +snapshot + +unlink /1/2 +remount + +mkfile $2 /1/2 +snapshot diff --git a/meta/047-replace-mult-links.mac b/meta/047-replace-mult-links.mac new file mode 100644 index 0000000..375907a --- /dev/null +++ b/meta/047-replace-mult-links.mac @@ -0,0 +1,13 @@ +# replace a dir on same ino with a cdev with the same name, new has 2 links +mkdir $2 /2 +mkdir $1 /2/$a.1 +mkdir $3 /2/$a.1/3 +snapshot + +rmdir /2/$a.1/3 +remount + +mknod $3 /2/$a.1/3 c 100 101 +mkdir $4 /2/$a.2 +link /2/$a.1/3 /2/$a.2/3 +snapshot diff --git a/meta/048-replace-mult-links.mac b/meta/048-replace-mult-links.mac new file mode 100644 index 0000000..9af74b5 --- /dev/null +++ b/meta/048-replace-mult-links.mac @@ -0,0 +1,16 @@ +# replace a non-empty dir on same ino with a cdev with the same name, new has 2 +# links +mkdir $2 /2 +mkdir $1 /2/1 +mkdir $3 /2/1/3 +mkdir $5 /2/1/3/5 +snapshot + +rmdir /2/1/3/5 +rmdir /2/1/3 +remount + +mknod $3 /2/1/3 c 100 101 +mkdir $4 /2/4 +link /2/1/3 /2/4/3 +snapshot diff --git a/meta/049-replace-mult-links-dir.mac b/meta/049-replace-mult-links-dir.mac new file mode 100644 index 0000000..45049b1 --- /dev/null +++ b/meta/049-replace-mult-links-dir.mac @@ -0,0 +1,10 @@ +# replace a file with multiple links by a dir of the same name +mkdir $2 /2 +mkfile $1 /2/$a.1 +link /2/$a.1 /2/$a.2 +snapshot + +unlink /2/$a.1 +unlink /2/$a.2 +mkdir $3 /2/$a.1 +snapshot diff --git a/meta/050-replace-mult-links-dir.mac b/meta/050-replace-mult-links-dir.mac new file mode 100644 index 0000000..a70bb5c --- /dev/null +++ b/meta/050-replace-mult-links-dir.mac @@ -0,0 +1,13 @@ +# replace a file with multiple links by a dir of the same name with the same +# inode +mkdir $2 /2 +mkfile $1 /2/$a.1 +link /2/$a.1 /2/$a.2 +snapshot + +unlink /2/$a.1 +unlink /2/$a.2 +remount + +mkdir $1 /2/$a.1 +snapshot diff --git a/meta/051-dir-to-file-same-name.mac b/meta/051-dir-to-file-same-name.mac new file mode 100644 index 0000000..3f76975 --- /dev/null +++ b/meta/051-dir-to-file-same-name.mac @@ -0,0 +1,12 @@ +# replace dir by file under the same name +mkdir $2 /2 +mkdir $1 /2/1 +mkdir $4 /2/1/4 +snapshot + +rmdir /2/1/4 +rmdir /2/1 +remount + +mkfile $3 /2/1 +snapshot diff --git a/meta/052-file-to-dir.mac b/meta/052-file-to-dir.mac new file mode 100644 index 0000000..42696ed --- /dev/null +++ b/meta/052-file-to-dir.mac @@ -0,0 +1,13 @@ +# replace a file by a dir with subdir +mkdir $3 /3 +mkfile $2 /3/$a.1 +link /3/$a.1 /3/$a.2 +snapshot + +unlink /3/$a.1 +unlink /3/$a.2 +remount + +mkdir $2 /3/$a.1 +mkdir $1 /3/$a.1/1 +snapshot diff --git a/meta/053-replace-mult-links-dir.mac b/meta/053-replace-mult-links-dir.mac new file mode 100644 index 0000000..d724421 --- /dev/null +++ b/meta/053-replace-mult-links-dir.mac @@ -0,0 +1,13 @@ +# replace a file with multiple links by a dir of the same name with the same +# inode +mkdir $1 /1 +mkfile $2 /1/$a.1 +link /1/$a.1 /1/$a.2 +snapshot + +unlink /1/$a.1 +unlink /1/$a.2 +remount + +mkdir $2 /1/$a.1 +snapshot diff --git a/meta/054-rm-dir-file.mac b/meta/054-rm-dir-file.mac new file mode 100644 index 0000000..daf5f09 --- /dev/null +++ b/meta/054-rm-dir-file.mac @@ -0,0 +1,9 @@ +# remove a dir with a file +mkdir $1 /1 +mkdir $2 /1/2 +mkfile $3 /1/2/3 +snapshot + +unlink /1/2/3 +rmdir /1/2 +snapshot diff --git a/meta/055-rm-dir-dir.mac b/meta/055-rm-dir-dir.mac new file mode 100644 index 0000000..348d2f2 --- /dev/null +++ b/meta/055-rm-dir-dir.mac @@ -0,0 +1,9 @@ +# remove 2 dirs +mkdir $2 /2 +mkdir $1 /2/1 +mkdir $3 /2/1/3 +snapshot + +rmdir /2/1/3 +rmdir /2/1 +snapshot diff --git a/meta/056-rm-some-entries.mac b/meta/056-rm-some-entries.mac new file mode 100644 index 0000000..5954508 --- /dev/null +++ b/meta/056-rm-some-entries.mac @@ -0,0 +1,12 @@ +# remove some, but not all, entries from a dir +mkdir $3 /3 +mkdir $1 /3/$a.1 +mkdir $2 /3/2 +mkfile $4 /3/$a.3 +mkfile $5 /3/4 +snapshot + +rmdir /3/$a.1 +rmdir /3/2 +unlink /3/$a.3 +snapshot diff --git a/refgen.pl b/refgen.pl new file mode 100755 index 0000000..2603484 --- /dev/null +++ b/refgen.pl @@ -0,0 +1,437 @@ +#!/usr/bin/perl -w + +# Copyright (C) 2012 STRATO. All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public +# License v2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 021110-1307, USA. + +# +# commands: +# +# mkdir <ino> <name> [mode [owner [group]]] +# mkfile <ino> <name> [size [mode [owner [group]]]] +# mksparse <ino> <name> [size [mode [owner [group]]]] +# mknod <ino> <name> <b|c> <major> <minor> [mode [owner [group]]] +# mksymlink <ino> <name> <relative-src> [owner [group]] +# mkfifo <ino> <name> [mode [owner [group]]] +# link <src> <target> +# rename <src> <dst> +# unlink <name> +# rmdir <name> +# write <name> <offset> <length> +# truncate <name> <length> +# mtime <name> +# atime <name> +# chown <name> <uid>[:<gid>] +# chmod <name> <mod> +# truncate <name> [<length>] +# remount +# snapshot + +use strict; +use Getopt::Std; +use POSIX qw(mkfifo); +use Fcntl qw(:seek); + +my $btrfs = "/usr/local/bin/btrfs"; +$| = 1; +select STDERR; +$| = 1; +select STDOUT; + +my %opts = (); +my $allok = getopts("b:p:vz:", \%opts); +if (!$allok || !$opts{b} == !$opts{z} || !$opts{p}) { + die "usage: $0 -p path {-z zfs|-b btrfs}\n"; +} + +my $path = $opts{p}; +my $zfs_name = $opts{z}; +my $btrfs_dev = $opts{b}; +my @actions = (); +my %inodes = (); + +my $actions_i = 0; +while (local $_ = <STDIN>) { + chomp; + s/\s+/ /g; + next if !$_ || $_ eq " " || /^ ?#/; + push @{$actions[$actions_i]}, [$., $_]; + if (/^snapshot|remount$/) { + $actions_i++; + } +} + +my @state; +my $ln; +sub get_path($); +sub tempname($) { + my $ino = shift; + + die "ino not defined in line $ln\n" unless defined $ino; + return "$path/tempname-$ino"; +} + +sub mkpath($) { + my $p = shift; + + die "path not defined in line $ln\n" unless defined $p; + die "path $p does not begin with / in line $ln" unless $p =~ /^\//; + + return "$path$p"; +} + +sub expand_size($) { + my $size = shift; + my $e = 1; + my ($n, $m) = $size =~ /(\d+)([kKmMgGtTpP])?/; + return $n if (!defined $m); + $e *= 1024; + return $n * $e if ($m =~ /[kK]/); + $e *= 1024; + return $n * $e if ($m =~ /[mM]/); + $e *= 1024; + return $n * $e if ($m =~ /[gG]/); + $e *= 1024; + return $n * $e if ($m =~ /[tT]/); + $e *= 1024; + return $n * $e if ($m =~ /[pP]/); + die "invalid qualifier $m in size in line $ln"; +} + +if ($zfs_name) { + system("zfs destroy -r $zfs_name"); + system("zfs create -o mountpoint=$path $zfs_name"); + die "zfs create $zfs_name failed\n" if ($?); +} else { + system("mkfs.btrfs -L refgen-fs $btrfs_dev"); + die "mkfs.btrfs $btrfs_dev failed\n" if ($?); + system("mount -o noatime,inode_cache $btrfs_dev $path"); + die "mount $btrfs_dev failed\n" if ($?); +} +my $snap = 1; +my %names; + +foreach my $a (@actions) { + my @rules; + my @makes; + + foreach (@$a) { + my ($ln, $action) = @$_; + my ($a, @param) = split / /, $action; + push @rules, [$ln, $a, @param]; + if ($a =~ /^mk/) { + @makes[$param[0]] = [$ln, $a, @param]; + } + } + + # + # create all files/dirs we need + # + my @deletes; + for my $i (1 ... @makes-1) { + my $r = $makes[$i]; + if (defined $r) { + $ln = $r->[0]; + my $a = $r->[1]; + my $ino = $r->[2]; + my $name = tempname($ino); + my $mode = undef; + my $owner; + my $group; + if ($state[$i]->{created}) { + die "ino $ino already exist. Consider ". + "using remount in line $ln\n"; + } + print "create $i\n" if $opts{v}; + if ($a eq "mkdir") { + $mode = $r->[4] || 0750; + $owner = $r->[5] || "0"; + $group = $r->[6] || "0"; + mkdir($name) + or die "mkdir($name) failed ". + "in line $ln: $!\n"; + } elsif ($a eq "mksymlink") { + my $src = $r->[4]; + $owner = $r->[5] || "0"; + $group = $r->[6] || "0"; + symlink($src, $name) + or die "symlink($src, $name) failed ". + "in line $ln: $!\n"; + } elsif ($a eq "mkfifo") { + $mode = $r->[4] || 0750; + $owner = $r->[5] || "0"; + $group = $r->[6] || "0"; + mkfifo($name, $mode) + or die "mkfifo($name, $mode) failed ". + "in line $ln: $!\n"; + } elsif ($a eq "mkfile" || $a eq "mksparse") { + my $buf; + my $size = $r->[4] || 654321; + $mode = $r->[5] || 0640; + $owner = $r->[6] || "0"; + $group = $r->[7] || "0"; + $size = expand_size($size); + open(FH, ">$name") + or die "creating file $name ". + "failed in line $ln: $!\n"; + if ($a eq "mkfile") { + if ($size > 104857600) { + die "size > 100M in line $ln\n"; + } + open(R, "</dev/urandom") + or die "open /dev/urandom ". + "failed: $!\n"; + sysread(R, $buf, $size) + or die "read from /dev/urandom". + " failed: $!\n"; + close(R) + or die "close /dev/urandom ". + "failed: $!\n"; + syswrite(FH, $buf) + or die "writing to file $name ". + "failed in line ". + "$ln: $!\n"; + } else { + seek(FH, $size, SEEK_SET) + or die "failed to seek to ". + "$size in $name at ". + "line $ln: $!\n"; + truncate(FH, $size) + or die "failed to truncate to ". + "$size in $name at ". + "line $ln: $!\n"; + } + close(FH) + or die "closing file $name ". + "failed in line $ln: $!\n"; + } elsif ($a eq "mknod") { + my $buf; + my $type = $r->[4]; + my $major = $r->[5]; + my $minor = $r->[6]; + $mode = $r->[7] || 0640; + $owner = $r->[8] || "0"; + $group = $r->[9] || "0"; + system("mknod $name $type $major $minor"); + die "mknod $name $type $major $minor failed ". + "in line $ln\n" if ($?); + } else { + die "invalid action $a in line $ln\n"; + } + if (defined $mode) { + chmod($mode, $name) + or die sprintf("chmod(0%o, %s) failed ". + "in line $ln: $!\n", + $mode, $name); + } + system("chown", "-h", "$owner:$group", $name); + die "chown($owner, $group, $name) failed ". + "in line $ln\n" if $?; + + $state[$i]->{created} = 1; + $state[$i]->{links} = 1; + $state[$i]->{tempname} = $name; + my $fs_ino = (lstat $name)[1]; + if (!exists $inodes{$ino}) { + $inodes{$ino} = $fs_ino; + } elsif ($fs_ino != $inodes{$ino}) { + die "fatal error: inode $ino became inode ". + "$inodes{$ino} earlier, while now ". + "(line $ln) it is $fs_ino\n"; + } + } elsif (!defined $state[$i]->{created}) { + my $name = tempname($i); + + print "temp create $i\n" if $opts{v}; + mkdir($name) + or die "creating temp dir $name failed: $!\n"; + push @deletes, $name; + } + } + foreach my $name (@deletes) { + print "temp delete $name\n" if $opts{v}; + rmdir $name; + } + + # + # replay actions + # + foreach my $r (@rules) { + $ln = $r->[0]; + my $a = $r->[1]; + + if ($a =~ /^mk/) { + my $ino = $r->[2]; + my $p = mkpath($r->[3]); + print "move $ino to $p\n" if $opts{v}; + rename($state[$ino]->{tempname}, $p) + or die "move $ino to $p failed ". + "in line $ln: $!\n"; + delete $state[$ino]->{tempname}; + $state[$ino]->{names}->{$p} = 1; + $names{$p} = $ino; + } elsif ($a eq "link") { + my $source = mkpath($r->[2]); + my $target = mkpath($r->[3]); + my $ino = $names{$source}; + die "source $source not found in line $ln\n" + unless $ino; + print "link $source to $target\n" if $opts{v}; + link($source, $target) + or die "link($source, $target) failed ". + "in line $ln\n"; + $state[$ino]->{names}->{$target} = 1; + ++$state[$ino]->{links}; + $names{$target} = $ino; + } elsif ($a eq "rename") { + my $src = mkpath($r->[2]); + my $dst = mkpath($r->[3]); + my $ino = $names{$src}; + die "source $src not found in line $ln\n" unless $ino; + print "move $src to $dst\n" if $opts{v}; + foreach my $n (sort keys %names) { + if ($n =~ /^$src(\/.*)/) { + my $new = $dst.$1; + my $ino = $names{$n}; + print "rewrite $n to $new, ino $ino\n" + if $opts{v}; + delete $state[$ino]->{names}->{$n}; + $state[$ino]->{names}->{$new} = 1; + delete $names{$n}; + $names{$new} = $ino; + } + delete $state[$ino]->{names}->{$src}; + $state[$ino]->{names}->{$dst} = 1; + delete $names{$src}; + $names{$dst} = $ino; + } + rename($src, $dst) + or die "rename $src to $dst failed ". + "in line $ln: $!\n"; + } elsif ($a eq "unlink") { + my $p = mkpath($r->[2]); + my $ino = $names{$p}; + die "source $p not found in line $ln\n" unless $ino; + print "unlink $p\n" if $opts{v}; + unlink($p) + or die "unlink $p failed in line $ln: $!\n"; + delete $names{$p}; + delete $state[$ino]->{names}->{$p}; + if (--$state[$ino]->{links} == 0) { + print "last ref to ino $ino\n" if $opts{v}; + delete $state[$ino]; + } + } elsif ($a eq "rmdir") { + my $p = mkpath($r->[2]); + my $ino = $names{$p}; + die "source $p not found in line $ln\n" unless $ino; + print "rmdir $p\n" if $opts{v}; + rmdir($p) + or die "rmdir $p failed in line $ln: $!\n"; + delete $names{$p}; + delete $state[$ino]->{names}->{$p}; + if (--$state[$ino]->{links} == 0) { + print "last ref to ino $ino\n"; + delete $state[$ino]; + } + } elsif ($a eq "write") { + my $buf; + my $p = mkpath($r->[2]); + my $off = $r->[3]; + my $len = $r->[4]; + $len = expand_size($len); + $off = expand_size($off); + open(R, "</dev/urandom") + or die "open /dev/urandom failed: $!\n"; + sysread(R, $buf, $len) + or die "read from /dev/urandom failed: $!\n"; + close(R) + or die "close /dev/urandom failed: $!\n"; + open(FH, "+<$p") + or die "open $p for writing failed ". + "in line $ln: $!\n"; + seek(FH, $off, 0) + or die "seek($p, $off) failed ". + "in line $ln: $!\n"; + syswrite(FH, $buf) + or die "writing to file $p failed ". + "in line $ln: $!\n"; + close(FH) + or die "close of $p failed in line $ln: $!\n"; + } elsif ($a eq "truncate") { + my $p = mkpath($r->[2]); + my $size = $r->[3]; + $size = expand_size($size); + truncate($p, $size) + or die "truncate $p to $size failed ". + "in line $ln: $!\n"; + } elsif ($a eq "chmod") { + my $p = mkpath($r->[2]); + my $mode = $r->[3]; + chmod(oct($mode), $p) + or die "chmod $p to $mode failed ". + "in line $ln: $!\n"; + } elsif ($a eq "chown") { + my $p = mkpath($r->[2]); + my ($uid, $gid) = (split(/:/, $r->[3]), 0); + system("chown", "-h", "$uid:$gid", $p); + die "chown $p to $uid:$gid failed ". + "in line $ln\n" if $?; + } elsif ($a eq "atime") { + my $p = mkpath($r->[2]); + qx{touch -a $p}; + if ($?) { + die "atime update $p failed in line $ln\n"; + } + } elsif ($a eq "mtime") { + my $p = mkpath($r->[2]); + qx{touch -m $p}; + if ($?) { + die "mtime update $p failed in line $ln\n"; + } + } elsif ($a eq "truncate") { + my $p = mkpath($r->[2]); + my $size = $r->[3] ? $r->[3] : 0; + truncate($p, $size) + or die "truncate $p to $size failed ". + "in line $ln: $!\n"; + } elsif ($a eq "remount") { + # ignore + } elsif ($a eq "snapshot") { + print "create snapshot $snap\n" if $opts{v}; + if ($zfs_name) { + system("zfs snapshot $zfs_name\@$snap"); + die "creating snapshot $zfs_name\@$snap ". + "failed\n" if ($?); + } else { + system("$btrfs subvol snap -r $path ". + "$path/\@$snap"); + die "creating snapshot $path\@$snap failed\n" + if ($?); + } + ++$snap; + } + } + + # + # umount/mount + # + if ($zfs_name) { + system("zfs umount $zfs_name"); + die "zfs umount $zfs_name failed\n" if ($?); + system("zfs mount $zfs_name"); + die "zfs mount $zfs_name failed\n" if ($?); + } +} @@ -0,0 +1,368 @@ +#!/usr/bin/perl + +# Copyright (C) 2012 STRATO. All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public +# License v2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 021110-1307, USA. + +use strict; +use warnings; +use Getopt::Std; +use Time::HiRes qw(time); +use IPC::Open3; +use Symbol qw(gensym); + +my @fs = qw(btrfs zfs); +my %opts = (D => "/mnt/dst", S => "/mnt/src"); + +sub usage { + print STDERR <<EO_USAGE; +$0 -s source -d destination [...] + -d destination type:device (required; always local) + -D destination mountpoint (default $opts{D}) + -p pedanctic mode (stop on error) + -q quiet + -r remote host (needs ssh root login) + -s source type:device (required; remote with -r, otherwise local) + -S source mountpoint (default $opts{S}) + -t test spec (e.g. "1,2,3", "1-3,6:1,7:2-4,8-12") + -v verbose +type must be one of: @fs +EO_USAGE + exit(shift); +} + +sub out { + print @_ if !$opts{q}; +} + +sub verbose { + print @_ if $opts{v}; +} + +sub do_local { + my @out = capture_local(@_); + print map {"$_\n"} @out if $opts{v}; +} + +my $remote_host; +sub args_for_remote { + return @_ if !$remote_host; + return ("ssh", "-l", "root", $remote_host, + map {ref($_) ? $_ : quotemeta($_)} @_); +} + +sub do_remote { + return do_local(args_for_remote(@_)); +} + +sub capture_local { + # we only open() to local handles and let perl do the close()s + my $args = pop if (ref $_[-1]); + verbose("running @_... "); + my $t_start = time; + local (*OUTFILE, *INFILE, *DATAFILE); + my ($fd_in, $fd_out, $fd_out_err, $fd_infile) = (); + my $data = ""; + if ($args->{input}) { + open(INFILE, "<", $args->{input}) + or die "\nERROR from command: @_\ncannot redirect ". + "input from $args->{input}: $!\n"; + $fd_infile = \*INFILE; + } + if ($args->{data}) { + open(DATAFILE, "<", $args->{data}) + or die "\nERROR from command: @_\ncannot get input ". + "data from $args->{data}: $!\n"; + } + if ($args->{output}) { + open(OUTFILE, ">", $args->{output}) + or die "\nERROR from command: @_\ncannot redirect ". + "output to $args->{output}: $!\n"; + $fd_out_err = gensym; + } + my $pid = open3($fd_in, + $args->{output} ? ">&OUTFILE" : $fd_out, + $fd_out_err, @_); + if (!$args->{input}) { + close($fd_in); + undef $fd_in; + } + $fd_out = $fd_out_err if $args->{output}; + my ($rin, $win, $rout, $wout, @out) = ("", ""); + vec($rin, fileno($fd_out), 1) = 1 if $fd_out; + vec($win, fileno($fd_in), 1) = 1 if $fd_in; + while ($fd_in || $fd_out) { + $rout = $rin; + $wout = $win; + select($fd_out ? $rout : undef, $fd_in ? $wout : undef, + undef, undef); + if ($fd_in && $wout eq $win) { + my $buf = <$fd_infile>; + if (defined $buf) { + syswrite($fd_in, $buf) + or die "\nERROR from command: @_\n". + "cannot write to it: $!\n"; + } elsif ($args->{data} && $fd_infile != \*DATAFILE) { + my $ret = syswrite($fd_in, "\n__END__\n"); + die if $ret != 9; + $fd_infile = \*DATAFILE; + } else { + undef $fd_in; + } + } + if ($fd_out && $rout eq $rin) { + my $ret = sysread($fd_out, $data, 8192, length($data)); + if (!defined $ret) { + die "\nERROR from command: @_\ncannot ". + "read from it: $!\n"; + } + undef $fd_out if !$ret; + } + } + waitpid($pid, 0); + if ($? && !$args->{mayfail}) { + die "\nERROR from command: @_\noutput:\n$data\n"; + } + my $t_elapsed = int((time - $t_start) * 100)/100; + verbose("done ($t_elapsed sec)\n"); + return $data; +} + +sub capture_remote { + return capture_local(args_for_remote(@_)); +} + +sub ignore_local { + capture_local(@_, {mayfail => 1}); +} + +sub ignore_remote { + return ignore_local(args_for_remote(@_)); +} + +my $p_refgen = "./refgen.pl"; +my $p_refgen_actions = "./actions"; +my $p_fssum = "fssum"; +my $p_sumfile = "/tmp/fssum.out"; +my $p_btrfs = "btrfs"; +my $p_zfs = "zfs"; +my $p_diff_outfile = "/tmp/fartest"; +my $p_perl = "perl"; + +my $allok = getopts("d:D:hpqr:s:S:t:v", \%opts); + +if (!$opts{d}) { + print STDERR "missing -d destination\n"; + $allok = 0; +} + +if (!$opts{s}) { + print STDERR "missing -s source\n"; + $allok = 0; +} + +my $allowed_fs = join("|", @fs); + +if (!$allok || $opts{h}) { + usage(!$opts{h}); +} + +if ($opts{d} !~ /^([^:]+):(.*)$/) { + print STDERR "wrong format for -d destination ($opts{d})\n"; + $allok = 0; +} +my ($dst_type, $dst_dev) = ($1, $2); +if (!grep $dst_type, @fs) { + print STDERR "unsupported type for destination\n"; + $allok = 0; +} +if ($opts{s} !~ /^([^:]+):(.*)$/) { + print STDERR "wrong format for -s source ($opts{s})\n"; + $allok = 0; +} +my ($src_type, $src_dev) = ($1, $2); +if (!grep $src_type, @fs) { + print STDERR "unsupported type for source\n"; + $allok = 0; +} +if ($dst_type eq "zfs") { + print STDERR "zfs destination type not implemented\n"; + $allok = 0; +} +my $dst_mnt = $opts{D}; +my $src_mnt = $opts{S}; +my $verbose = $opts{v} ? "-v" : ""; +$remote_host = $opts{r}; + +my ($src_type_opt, @p_send, $src_send, $dst_subvol, $send_incr_opt); +if ($src_type eq "zfs") { + $src_type_opt = "-z"; + @p_send = qw(zfs send -F); + $send_incr_opt = "-i"; + $src_send = $src_dev; + $dst_subvol = $src_dev; + $dst_subvol =~ s{.*/}{}; +} elsif ($src_type eq "btrfs") { + $src_type_opt = "-b"; + @p_send = qw(btrfs send); + $send_incr_opt = "-i"; + $src_send = "$src_mnt/"; + $dst_subvol = ""; +} else { + die; +} + +sub snapshot_path { + return "$src_mnt/\@$_[0]" if ($src_type eq "btrfs"); + return "$src_mnt/.zfs/snapshot/$_[0]" if ($src_type eq "zfs"); + die; +} + +sub parse_range { + my $range = shift; + if ($range !~ /^(\d+)(?:-(\d+))?$/) { + die qq{failed to parse test spec at "$range"}; + } + my $start = $1; + my $end = defined $2 ? $2 : $start; + if ($start > $end) { + die qq{reverse range in test spec at "$range"}; + } + return ($start, $end); +} + +my $number_of_digits_in_expand = 3; +sub subtest_range { + my ($s_start, $s_end) = @_; + my $n = $number_of_digits_in_expand; + my $test_spec = ":(?:"; + for ($s_start .. $s_end) { + $test_spec .= sprintf("%0${n}d", $_); + $test_spec .= "|"; + } + chop $test_spec; + $test_spec .= ")-"; + return $test_spec; +} + +my $test_spec = ""; +if ($opts{t}) { + my $n = $number_of_digits_in_expand; + foreach (split /,/, $opts{t}) { + my ($tnum, $subtests) = split /:/; + my ($t_start, $t_end) = $tnum ? parse_range($tnum) : (1, 1); + my ($s_start, $s_end) = parse_range($subtests) if $subtests; + for ($t_start .. $t_end) { + $test_spec .= "^"; + $test_spec .= $tnum ? sprintf("%0${n}d", $_) : "\\d+"; + if ($subtests) { + $test_spec .= subtest_range($s_start, $s_end); + } else { + $test_spec .= ":"; + } + $test_spec .= "|"; + } + } + chop $test_spec; + eval { + $test_spec = qr{$test_spec}; + }; + if ($@) { + die "failed to compile regexp for test spec: $@" + } +} + +ignore_local("umount", $dst_mnt); +ignore_remote("umount", $src_mnt); + +my @files = split /\n/, capture_local("ls", $p_refgen_actions); +my @failed_tests = (); +my $cnt = 0; +my $ex_ret = 0; +foreach my $action_file (@files) { + if ($test_spec && $action_file !~ $test_spec) { + next; + } + my $i = 0; + my $n_snaps = 0; + my $t_start = time; + eval { + out("running check $action_file... "); + if ($cnt++) { + do_local("umount", $dst_mnt); + do_remote("umount", $src_mnt); + } + do_local("mkfs.btrfs", "-L", "fits", $dst_dev); + do_local("mount", "-o", "noatime", $dst_dev, $dst_mnt); + + do_remote($p_perl, "-", "-p", $src_mnt, $src_type_opt, $src_dev, + ($verbose ? $verbose : ()), { + input => $p_refgen, + data => "$p_refgen_actions/$action_file", + }); + + $n_snaps = capture_local("grep", "-c", "snapshot", + "$p_refgen_actions/$action_file"); + chomp $n_snaps; + + my @exclude = (); + for ($i = 1; $i <= $n_snaps; $i++) { + my ($data_file); + if ($i == 1) { + $data_file = $p_diff_outfile.".full"; + do_remote(@p_send, "$src_send\@1", + {output => $data_file}); + } else { + my $prev = $i - 1; + $data_file = $p_diff_outfile.".incr.$prev-$i"; + do_remote(@p_send, $send_incr_opt, + "$src_send\@$prev", "$src_send\@$i", + {output => $data_file}); + } + + do_local($p_btrfs, "receive", + ($verbose ? $verbose : ()), + $dst_mnt, {input => $data_file}); + + my @x = map { ("-x", "$src_mnt/\@$i/\@$_") } @exclude + if $src_type eq "btrfs"; + do_remote($p_fssum, @x, "-f", snapshot_path($i), + {output => $p_sumfile}); + do_local($p_fssum, "-r", $p_sumfile, + "$dst_mnt/$dst_subvol\@$i"); + verbose("done\n"); + push @exclude, $i; + } + }; + + if ($@) { + die($@) if $opts{p}; + out($i ? "step $i/$n_snaps" : "preparation", " failed\n") + if !$verbose; + verbose($@); + $ex_ret++; + push @failed_tests, $action_file; + } else { + my $t_elapsed = int((time - $t_start) * 100)/100; + out("ok ($t_elapsed sec)\n"); + } +} + +if (@failed_tests) { + print "\nFailed tests: ", join(", ", @failed_tests), "\n"; +} else { + print "\nAll tests ok\n"; +} +exit($ex_ret); |