summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Schmidt <janosch@webgods.de>2012-11-01 15:36:41 +0100
committerJan Schmidt <janosch@webgods.de>2012-11-01 15:36:41 +0100
commit574be62b318a10613c646cf44fc750d5d4b7355a (patch)
treeb3d5ddce1eb6efc64a19ea979ddae33f5f71f6c0
parentc0b02d3d0ae46c3ad5e0c574515b8755b81d4330 (diff)
downloadfar-progs-574be62b318a10613c646cf44fc750d5d4b7355a.tar.gz
far-progs: test suite
This is the first snapshot of our automated test suite, currently supporting btrfs and zfs as a data source and btrfs as destination. Signed-off-by: Jan Schmidt <janosch@webgods.de>
-rw-r--r--.gitignore3
-rw-r--r--Makefile7
-rwxr-xr-xexpand.pl127
-rw-r--r--meta/001-file-create.mac6
-rw-r--r--meta/002-rename.mac7
-rw-r--r--meta/003-link-rename.mac13
-rw-r--r--meta/004-empty.mac3
-rw-r--r--meta/005-chmod.mac11
-rw-r--r--meta/006-chown.mac10
-rw-r--r--meta/007-mtime.mac10
-rw-r--r--meta/008-atime.mac10
-rw-r--r--meta/009-truncate.mac8
-rw-r--r--meta/010-symlink.mac15
-rw-r--r--meta/011-fifo.mac18
-rw-r--r--meta/012-mksparse.mac6
-rw-r--r--meta/013-sparse-truncate.mac11
-rw-r--r--meta/014-same-name.mac7
-rw-r--r--meta/015-new-dir.mac8
-rw-r--r--meta/016-mv-dir.mac11
-rw-r--r--meta/017-cross.mac11
-rw-r--r--meta/018-two-links.mac7
-rw-r--r--meta/019-rename-dir.mac9
-rw-r--r--meta/020-tempname.mac11
-rw-r--r--meta/021-file-to-dir.mac12
-rw-r--r--meta/022-dir-to-file.mac13
-rw-r--r--meta/023-dir-name-exchange.mac11
-rw-r--r--meta/024-dir-tempname.mac11
-rw-r--r--meta/025-dir-tempname.mac11
-rw-r--r--meta/026-mknod.mac9
-rw-r--r--meta/027-rm-dir-delayed-rename.mac12
-rw-r--r--meta/028-replace-file.mac10
-rw-r--r--meta/029-dir-to-file.mac10
-rw-r--r--meta/030-three-links.mac13
-rw-r--r--meta/031-replace-dir.mac12
-rw-r--r--meta/032-replace-dir.mac13
-rw-r--r--meta/033-replace-file.mac10
-rw-r--r--meta/034-replace-dir-same-name.mac13
-rw-r--r--meta/035-replace-file-cdev.mac10
-rw-r--r--meta/036-replace-file-cdev.mac8
-rw-r--r--meta/037-replace-file-cdev.mac10
-rw-r--r--meta/038-replace-mult-links.mac13
-rw-r--r--meta/039-no-zfs-parent.mac8
-rw-r--r--meta/040-replace-mult-links.mac14
-rw-r--r--meta/041-replace-mult-links.mac17
-rw-r--r--meta/042-replace-mult-links.mac13
-rw-r--r--meta/043-replace-mult-links.mac14
-rw-r--r--meta/044-replace-mult-links.mac18
-rw-r--r--meta/045-replace-mult-links.mac15
-rw-r--r--meta/046-replace-file.mac10
-rw-r--r--meta/047-replace-mult-links.mac13
-rw-r--r--meta/048-replace-mult-links.mac16
-rw-r--r--meta/049-replace-mult-links-dir.mac10
-rw-r--r--meta/050-replace-mult-links-dir.mac13
-rw-r--r--meta/051-dir-to-file-same-name.mac12
-rw-r--r--meta/052-file-to-dir.mac13
-rw-r--r--meta/053-replace-mult-links-dir.mac13
-rw-r--r--meta/054-rm-dir-file.mac9
-rw-r--r--meta/055-rm-dir-dir.mac9
-rw-r--r--meta/056-rm-some-entries.mac12
-rwxr-xr-xrefgen.pl437
-rwxr-xr-xtest.pl368
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
diff --git a/Makefile b/Makefile
index eeea820..7254492 100644
--- a/Makefile
+++ b/Makefile
@@ -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 ($?);
+ }
+}
diff --git a/test.pl b/test.pl
new file mode 100755
index 0000000..aa39835
--- /dev/null
+++ b/test.pl
@@ -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);