fuse
changeset 18 4a01ae9db5c4
parent 16 867424a1db61
equal deleted inserted replaced
17:0e5a8a5f4674 18:4a01ae9db5c4
     4 use strict;
     4 use strict;
     5 use warnings;
     5 use warnings;
     6 use autodie qw(:all);
     6 use autodie qw(:all);
     7 use Getopt::Long;
     7 use Getopt::Long;
     8 use Fuse;
     8 use Fuse;
     9 use POSIX qw(setpgid);
     9 use POSIX qw(setpgid :errno_h);
       
    10 use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
    10 use Pod::Usage;
    11 use Pod::Usage;
    11 use Hash::Util qw(lock_keys);
    12 use Hash::Util qw(lock_keys);
       
    13 use File::Temp;
       
    14 use DB_File;
    12 use File::Basename;
    15 use File::Basename;
    13 
    16 
    14 my %o = (
    17 my %o = (
    15     debug => undef,
    18     debug => undef,
    16     detach => 1,
    19     detach => 1,
       
    20     tmp => undef,
    17 ); lock_keys %o;
    21 ); lock_keys %o;
    18 
    22 
    19 use constant ME => basename $0;
    23 use constant ME => basename $0;
       
    24 my ($DATA, $IDX);
       
    25 
       
    26 sub tie_vars;
    20 
    27 
    21 MAIN: {
    28 MAIN: {
    22 
    29 
    23     GetOptions(
    30     GetOptions(
    24 	"d|debug!" => \$o{debug},
    31 	"d|debug!" => \$o{debug},
    25 	"detach!" => \$o{detach},
    32 	"detach!" => \$o{detach},
       
    33 	"tmp:s" =>  sub { $o{tmp} = length  $_[1] ? $_[1] : $ENV{TMP}// "/tmp" },
    26 	"h|help" => sub { pod2usage(-verbose => 1, -exit => 0) },
    34 	"h|help" => sub { pod2usage(-verbose => 1, -exit => 0) },
    27 	"m|man" =>  sub { pod2usage(-verbose => 2, -exit => 0,
    35 	"m|man" =>  sub { pod2usage(-verbose => 2, -exit => 0,
    28 		-noperlpod => system("perldoc -V 1>/dev/null 2>&1")) },
    36 		-noperlpod => system("perldoc -V 1>/dev/null 2>&1")) },
    29 	) and @ARGV == 2 or pod2usage;
    37 	) and @ARGV == 2 or pod2usage;
    30 
    38 
    31     my ($src, $mp) = @ARGV;
    39     my ($src, $mp) = @ARGV;
    32 
    40 
    33     $fs::DATA = "$src/data";
    41     $DATA = "$src/data";
    34     $fs::IDX = "$src/idx";
    42     $IDX = "$src/idx";
    35 
    43 
    36     die ME.": $fs::DATA: $!" if not -d $fs::DATA;
    44     die ME.": $DATA: $!" if not -d $DATA;
    37     die ME.": $fs::IDX: $!" if not -d $fs::IDX;
    45     die ME.": $IDX: $!" if not -d $IDX;
    38 
    46 
    39     if (!$o{debug} and $o{detach}) {
    47     if (!$o{debug} and $o{detach}) {
    40 	fork() and exit;
    48 	fork() and exit;
    41 	$0 = "FUSE $src $mp";
    49 	$0 = "FUSE $src $mp";
    42 	open(STDOUT => ">/dev/null");
    50 	open(STDOUT => ">/dev/null");
    43 	open(STDIN => "/dev/null");
    51 	open(STDIN => "/dev/null");
    44 
    52 
    45 	setpgid($$ => $$);
    53 	setpgid($$ => $$);
    46     }
    54     }
    47 
    55 
       
    56     tie_vars $o{tmp};
    48 
    57 
    49     Fuse::main(mountpoint => $mp,
    58     Fuse::main(mountpoint => $mp,
    50 	debug => $o{debug} // 0,
    59 	debug => $o{debug} // 0,
    51 	getattr => "fs::getattr",
    60 	getattr => \&getattr,
    52 	getdir => "fs::getdir",
    61 	getdir => \&getdir,
    53 	open => "fs::openfile",
    62 	open => \&openfile,
    54 	read => "fs::readbuffer",
    63 	read => \&readbuffer,
    55 	write => "fs::writebuffer",
    64 	write => \&writebuffer,
    56 	);
    65 	);
    57 
    66 
    58 }
    67     exit;
    59 
    68 
    60 { package fs;
    69 }
    61   use strict;
    70 
    62   use warnings;
    71 # not the fuse functions
    63   use POSIX qw(:errno_h); 
    72 
    64   use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
    73 {
    65   use autodie qw(:all);
    74     my (%IMAGE, %DIRTY);
    66 
    75 
    67   our ($ROOT, $DATA, $IDX);
    76 sub tie_vars {
    68   my %IMAGE;
    77     return if not defined $_[0];
    69   my %DIRTY;
    78     my $file = -d $_[0] ? File::Temp->new(DIR => shift, TEMPLATE => "tmp.fuse.XXXXXX")->filename : shift;
    70 
    79     tie %DIRTY, "DB_File" => $file
    71     sub getattr {
    80 	or die "Can't tie to $file: $!\n";
    72 	my $path = $IDX . shift;
    81 }
    73 	return stat $path if -d $path;
    82 
    74 	# rest are the idx
    83 sub getattr {
    75 	my @attr = stat $path or return -(ENOENT);
    84     my $path = $IDX . shift;
    76 	my %meta = _get_meta($path);
    85     return stat $path if -d $path;
    77 	$attr[7] = $meta{devsize};
    86     my @attr = stat $path or return -(ENOENT);
    78 	$attr[9] = $meta{timestamp};
    87     my %meta = _get_meta($path);
    79 	$attr[2] &= ~0222;		# r/o
    88     $attr[7] = $meta{devsize};
    80 	return @attr;
    89     $attr[9] = $meta{timestamp};
    81     }
    90     $attr[2] &= ~0222;		# r/o
    82 
    91     return @attr;
    83     sub getdir {
    92 }
    84 	my $path = $IDX . shift;
    93 
    85 	opendir(my $dh, $path) or return 0;
    94 sub getdir {
    86 	return (readdir($dh), 0);
    95     my $path = $IDX . shift;
    87     }
    96     opendir(my $dh, $path) or return 0;
    88 
    97     return (readdir($dh), 0);
    89     sub openfile {
    98 }
    90 	my $path = $IDX . shift;
    99 
    91 	return 0 if exists $IMAGE{$path};
   100 sub openfile {
    92 	$IMAGE{$path}{meta} = { _get_meta($path) };
   101     my $path = $IDX . shift;
    93 	$IMAGE{$path}{blocklist} = {};
   102     return 0 if exists $IMAGE{$path};
    94 
   103     $IMAGE{$path}{meta} = { _get_meta($path) };
    95 	open(my $fh => $path);
   104     $IMAGE{$path}{blocklist} = {};
    96 	{   # the file header
   105 
    97 	    local $/ = "";
   106     # skip the file header
    98 	    scalar <$fh>;
   107     open(my $fh => $path);
    99 	}
   108     {   local $/ = ""; scalar <$fh> }
   100 	while (<$fh>) {
   109 
   101 	    /^#/ and last;
   110     # should check for the format
   102 	    my ($block, $cs, $file) = split;
   111     # $IMAGE{$path}{meta}{format}
   103 	    $block-- if not $IMAGE{$path}{meta}{format};
   112 
   104 	    $IMAGE{$path}{blocklist}{$block} = $file;
   113     # now read the block list
   105 	}
   114     while (<$fh>) {
   106 	close $fh;
   115 	/^#/ and last;
   107 	return 0;
   116 	my ($block, $cs, $file) = split;
   108     }
   117 	$IMAGE{$path}{blocklist}{$block} = $file;
   109 
   118     }
   110     sub readbuffer {
   119     close $fh;
   111 	my $path = $IDX . shift;
   120     return 0;
   112 	my ($size, $offset) = @_;
   121 }
   113 	my $finfo = $IMAGE{$path} or die "File $path is not opened!";
   122 
   114 	return "" if $offset >= $finfo->{meta}{devsize};
   123 sub readbuffer {
   115 
   124     my $path = $IDX . shift;
   116 	my $buffer = "";
   125     my ($size, $offset) = @_;
   117 	for (my $need = $size; $need > 0; $need = $size - length($buffer)) {
   126     my $finfo = $IMAGE{$path} or die "File $path is not opened!";
   118 	    $buffer .= _readblock($finfo, $need, $offset + length($buffer));
   127     return "" if $offset >= $finfo->{meta}{devsize};
   119 	}
   128 
   120 
   129     my $buffer = "";
   121 	return $buffer;
   130     for (my $need = $size; $need > 0; $need = $size - length($buffer)) {
   122     }
   131 	$buffer .= _readblock($finfo, $need, $offset + length($buffer));
   123 
   132     }
   124     sub _readblock {
   133 
   125 	my ($finfo, $size, $offset) = @_;
   134     return $buffer;
   126 
   135 }
   127 	my $block = int($offset / $finfo->{meta}{blocksize});
   136 
   128 	my $blockoffset = $offset % $finfo->{meta}{blocksize};
   137 sub _readblock {
   129 
   138     my ($finfo, $size, $offset) = @_;
   130 	my $length = $finfo->{meta}{blocksize} - $blockoffset;
   139 
   131 	$length = $size if $size <= $length;
   140     my $block = int($offset / $finfo->{meta}{blocksize});
   132 
   141     my $blockoffset = $offset % $finfo->{meta}{blocksize};
   133 	if (exists $DIRTY{$finfo.$block}) {
   142 
   134 	    return substr $DIRTY{$finfo.$block}, $blockoffset, $length;
   143     my $length = $finfo->{meta}{blocksize} - $blockoffset;
   135 	}
   144     $length = $size if $size <= $length;
   136 
   145 
   137 	my $fn = "$DATA/" . $finfo->{blocklist}{$block};
   146     if (exists $DIRTY{$finfo.$block}) {
   138 	if (-e $fn) {
   147 	return substr $DIRTY{$finfo.$block}, $blockoffset, $length;
   139 		open(my $fh => $fn);
   148     }
   140 		binmode($fh);
   149 
   141 		seek($fh => $blockoffset, 0) or die "seek: $!";
   150     my $fn = "$DATA/" . $finfo->{blocklist}{$block};
   142 		local $/ = \$length;
   151     if (-e $fn) {
   143 		return scalar <$fh>;
   152 	    open(my $fh => $fn);
   144 	}
   153 	    binmode($fh);
   145 	elsif (-e "$fn.gz") {
   154 	    seek($fh => $blockoffset, 0) or die "seek: $!";
   146 		open(my $fh => "$fn.gz");
   155 	    local $/ = \$length;
   147 		binmode($fh);
   156 	    return scalar <$fh>;
   148 		my $buffer;
   157     }
   149 		gunzip($fh => \$buffer)
   158     elsif (-e "$fn.gz") {
   150 			or die $GunzipError;
   159 	    open(my $fh => "$fn.gz");
   151 		close($fh);
   160 	    binmode($fh);
   152 		return substr($buffer, $blockoffset, $size);
   161 	    my $buffer;
   153 	}
   162 	    gunzip($fh => \$buffer)
   154 	
   163 		    or die $GunzipError;
   155 	die "$fn: $!\n";
   164 	    close($fh);
   156     }
   165 	    return substr($buffer, $blockoffset, $size);
   157 
   166     }
   158     sub writebuffer {
   167     
   159 	my $path = $IDX . shift;
   168     die "$fn: $!\n";
   160 	my ($buffer, $offset) = @_;
   169 }
   161 	my $size = length($buffer);
   170 
   162 	my $finfo = $IMAGE{$path} or die "File $path is not opened!";
   171 sub writebuffer {
   163 
   172     my $path = $IDX . shift;
   164 	for (my $written = 0; $written < $size;) {
   173     my ($buffer, $offset) = @_;
   165 	     # OPTIMIZE: we should not ask for writing more than the
   174     my $size = length($buffer);
   166 	     # blocksize
   175     my $finfo = $IMAGE{$path} or die "File $path is not opened!";
   167 	     my $n = _writeblock($finfo, substr($buffer, $written), $offset + $written) 
   176 
   168 		or return $written;
   177     for (my $written = 0; $written < $size;) {
   169 	     $written += $n;
   178 	 # OPTIMIZE: we should not ask for writing more than the
   170 	}
   179 	 # blocksize
   171 	return $size;
   180 	 my $n = _writeblock($finfo, substr($buffer, $written), $offset + $written) 
   172     }
   181 	    or return $written;
   173 
   182 	 $written += $n;
   174     sub _writeblock {
   183     }
   175 	my ($finfo, $buffer, $offset) = @_;
   184     return $size;
   176 	my $size = length($buffer);
   185 }
   177 
   186 
   178 	my $block = int($offset / $finfo->{meta}{blocksize});
   187 sub _writeblock {
   179 	my $blockoffset = $offset % $finfo->{meta}{blocksize};
   188     my ($finfo, $buffer, $offset) = @_;
   180 
   189     my $size = length($buffer);
   181 	if (not exists $DIRTY{$finfo.$block}) {
   190 
   182 	    $DIRTY{$finfo.$block} = _readblock(
   191     my $block = int($offset / $finfo->{meta}{blocksize});
   183 		    $finfo, 
   192     my $blockoffset = $offset % $finfo->{meta}{blocksize};
   184 		    $finfo->{meta}{blocksize}, 
   193 
   185 		    $block * $finfo->{meta}{blocksize});
   194     if (not exists $DIRTY{$finfo.$block}) {
   186 	}
   195 	$DIRTY{$finfo.$block} = _readblock(
   187 
   196 		$finfo, 
   188 	my $length = $finfo->{meta}{blocksize} - $blockoffset;
   197 		$finfo->{meta}{blocksize}, 
   189 	$length = $size if $size < $length;
   198 		$block * $finfo->{meta}{blocksize});
   190 
   199     }
   191 	substr($DIRTY{$finfo.$block}, $blockoffset, $length)
   200 
   192 	    = substr($buffer, 0, $length);
   201     my $length = $finfo->{meta}{blocksize} - $blockoffset;
   193 
   202     $length = $size if $size < $length;
   194 	return $length;
   203 
   195     }
   204     substr($DIRTY{$finfo.$block}, $blockoffset, $length)
   196 
   205 	= substr($buffer, 0, $length);
   197     sub _get_meta {
   206 
   198 	my $path = shift;
   207     return $length;
   199 	my %meta;
   208 }
   200 	open(my $fh => $path);
   209 
   201 	while(<$fh>) {
   210 sub _get_meta {
   202 	    last if /^$/;
   211     my $path = shift;
   203 	    /^(?<k>\S+):\s+(?<v>.*?)\s*$/ and do { $meta{$+{k}} = $+{v}; next; };
   212     my %meta;
   204 	}
   213     open(my $fh => $path);
   205 	return %meta;
   214     while(<$fh>) {
   206     }
   215 	last if /^$/;
   207 
   216 	/^(?<k>\S+):\s+(?<v>.*?)\s*$/ and do { $meta{$+{k}} = $+{v}; next; };
   208 }
   217     }
       
   218     return %meta;
       
   219 }
       
   220 
       
   221 }
       
   222 
   209 
   223 
   210 __END__
   224 __END__
   211 
   225 
   212 =head1 NAME
   226 =head1 NAME
   213 
   227 
   224 
   238 
   225 =head1 OPTIONS
   239 =head1 OPTIONS
   226 
   240 
   227 =over 4
   241 =over 4
   228 
   242 
       
   243 =item B<--tmp> [I<dir/>]
       
   244 
       
   245 Write dirty blocks into a buffer file in the specified tmp directory.
       
   246 If no directory is specified, the system default (usually F</tmp>) will
       
   247 be used. (default: no temp file)
       
   248 
       
   249 B<Beware>: The temporary file may get B<HUUGE>.
       
   250 
   229 =item B<-d>|B<--debug>
   251 =item B<-d>|B<--debug>
   230 
   252 
   231 Enables debugging output from B<Fuse>. When using this option,
   253 Enables debugging output from B<Fuse>. When using this option,
   232 B<Fuse> does not detach from the terminal. (default: off)
   254 B<Fuse> does not detach from the terminal. (default: off)
   233 
   255