9 use POSIX qw(ENOENT EISDIR EINVAL ENOSYS O_RDWR);
16 our $VERSION = '0.03';
20 Fuse::DBI - mount your database as filesystem and use it
25 Fuse::DBI->mount( ... );
27 See C<run> below for examples how to set parametars.
31 This module will use C<Fuse> module, part of C<FUSE (Filesystem in USErspace)>
32 available at L<http://sourceforge.net/projects/avf> to mount
33 your database as file system.
35 That will give you posibility to use normal file-system tools (cat, grep, vi)
36 to manipulate data in database.
38 It's actually opposite of Oracle's intention to put everything into database.
47 Mount your database as filesystem.
49 my $mnt = Fuse::DBI->mount({
50 filenames => 'select name from files_table as filenames',
52 update => 'sql update',
53 dsn => 'DBI:Pg:dbname=webgui',
54 user => 'database_user',
55 password => 'database_password'
65 sub fuse_module_loaded;
67 # evil, evil way to solve this. It makes this module non-reentrant. But, since
68 # fuse calls another copy of this script for each mount anyway, this shouldn't
81 carp "mount needs 'dsn' to connect to (e.g. dsn => 'DBI:Pg:dbname=test')" unless ($arg->{'dsn'});
82 carp "mount needs 'mount' as mountpoint" unless ($arg->{'mount'});
84 # save (some) arguments in self
85 foreach (qw(mount invalidate)) {
86 $self->{$_} = $arg->{$_};
89 foreach (qw(filenames read update)) {
90 carp "mount needs '$_' SQL" unless ($arg->{$_});
93 $ctime_start = time();
98 die "fork() failed: $!" unless defined $pid;
99 # child will return to caller
105 $dbh = DBI->connect($arg->{'dsn'},$arg->{'user'},$arg->{'password'}, {AutoCommit => 0, RaiseError => 1}) || die $DBI::errstr;
107 $sth->{'filenames'} = $dbh->prepare($arg->{'filenames'}) || die $dbh->errstr();
109 $sth->{'read'} = $dbh->prepare($arg->{'read'}) || die $dbh->errstr();
110 $sth->{'update'} = $dbh->prepare($arg->{'update'}) || die $dbh->errstr();
113 $self->{'sth'} = $sth;
115 $self->{'read_filenames'} = sub { $self->read_filenames };
116 $self->read_filenames;
118 $self->{'mounted'} = 1;
123 mountpoint=>$arg->{'mount'},
124 getattr=>\&e_getattr,
131 truncate=>\&e_truncate,
137 $self->{'mounted'} = 0;
139 exit(0) if ($arg->{'fork'});
147 Unmount your database as filesystem.
151 This will also kill background process which is translating
152 database to filesystem.
159 if ($self->{'mounted'}) {
160 system "fusermount -u ".$self->{'mount'} || croak "umount error: $!";
167 print STDERR "umount called by SIG INT\n";
173 return if (! $self->{'mounted'});
174 print STDERR "umount called by DESTROY\n";
178 =head2 fuse_module_loaded
180 Checks if C<fuse> module is loaded in kernel.
182 die "no fuse module loaded in kernel"
183 unless (Fuse::DBI::fuse_module_loaded);
185 This function in called by L<mount>, but might be useful alone also.
189 sub fuse_module_loaded {
191 die "can't start lsmod: $!" unless ($lsmod);
192 if ($lsmod =~ m/fuse/s) {
205 my $sth = $self->{'sth'} || die "no sth argument";
207 # create empty filesystem
214 # cont => "File 'a'.\n",
216 # ctime => time()-2000
220 # fetch new filename list from database
221 $sth->{'filenames'}->execute() || die $sth->{'filenames'}->errstr();
223 # read them in with sesible defaults
224 while (my $row = $sth->{'filenames'}->fetchrow_hashref() ) {
225 $files{$row->{'filename'}} = {
226 size => $row->{'size'},
227 mode => $row->{'writable'} ? 0644 : 0444,
228 id => $row->{'id'} || 99,
232 foreach (split(m!/!, $row->{'filename'})) {
233 # first, entry is assumed to be file
254 print "found ",scalar(keys %files)-scalar(keys %dirs)," files, ",scalar(keys %dirs), " dirs\n";
261 $file = '.' unless length($file);
266 my ($file) = filename_fixup(shift);
268 $file = '.' unless length($file);
269 return -ENOENT() unless exists($files{$file});
270 my ($size) = $files{$file}{size} || 1;
271 my ($dev, $ino, $rdev, $blocks, $gid, $uid, $nlink, $blksize) = (0,0,0,1,0,0,1,1024);
272 my ($atime, $ctime, $mtime);
273 $atime = $ctime = $mtime = $files{$file}{ctime} || $ctime_start;
275 my ($modes) = (($files{$file}{type} || 0100)<<9) + $files{$file}{mode};
277 # 2 possible types of return values:
278 #return -ENOENT(); # or any other error you care to
279 #print(join(",",($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)),"\n");
280 return ($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks);
284 my ($dirname) = shift;
286 # return as many text filenames as you like, followed by the retval.
287 print((scalar keys %files)." files total\n");
289 foreach my $f (sort keys %files) {
291 if ($f =~ s/^\E$dirname\Q\///) {
292 $out{$f}++ if ($f =~ /^[^\/]+$/);
295 $out{$f}++ if ($f =~ /^[^\/]+$/);
299 $out{'no files? bug?'}++;
301 print scalar keys %out," files in dir '$dirname'\n";
302 print "## ",join(" ",keys %out),"\n";
303 return (keys %out),0;
309 die "read_content needs file and id" unless ($file && $id);
311 $sth->{'read'}->execute($id) || die $sth->{'read'}->errstr;
312 $files{$file}{cont} = $sth->{'read'}->fetchrow_array;
313 $files{$file}{ctime} = time();
314 print "file '$file' content [",length($files{$file}{cont})," bytes] read in cache\n";
319 # VFS sanity check; it keeps all the necessary state, not much to do here.
320 my $file = filename_fixup(shift);
323 return -ENOENT() unless exists($files{$file});
324 return -EISDIR() unless exists($files{$file}{id});
326 read_content($file,$files{$file}{id}) unless exists($files{$file}{cont});
328 print "open '$file' ",length($files{$file}{cont})," bytes\n";
333 # return an error numeric, or binary/text string.
334 # (note: 0 means EOF, "0" will give a byte (ascii "0")
335 # to the reading program)
336 my ($file) = filename_fixup(shift);
337 my ($buf_len,$off) = @_;
339 return -ENOENT() unless exists($files{$file});
341 my $len = length($files{$file}{cont});
343 print "read '$file' [$len bytes] offset $off length $buf_len\n";
345 return -EINVAL() if ($off > $len);
346 return 0 if ($off == $len);
348 $buf_len = $len-$off if ($len - $off < $buf_len);
350 return substr($files{$file}{cont},$off,$buf_len);
354 print "transaction rollback\n";
355 $dbh->rollback || die $dbh->errstr;
356 print "invalidate all cached content\n";
357 foreach my $f (keys %files) {
358 delete $files{$f}{cont};
360 print "begin new transaction\n";
361 #$dbh->begin_work || die $dbh->errstr;
366 my $file = shift || die;
368 $files{$file}{ctime} = time();
375 if (!$sth->{'update'}->execute($cont,$id)) {
376 print "update problem: ",$sth->{'update'}->errstr;
380 if (! $dbh->commit) {
381 print "ERROR: commit problem: ",$sth->{'update'}->errstr;
385 print "updated '$file' [",$files{$file}{id},"]\n";
387 $$fuse_self->{'invalidate'}->() if (ref $$fuse_self->{'invalidate'});
393 my $file = filename_fixup(shift);
394 my ($buffer,$off) = @_;
396 return -ENOENT() unless exists($files{$file});
398 my $cont = $files{$file}{cont};
399 my $len = length($cont);
401 print "write '$file' [$len bytes] offset $off length ",length($buffer),"\n";
403 $files{$file}{cont} = "";
405 $files{$file}{cont} .= substr($cont,0,$off) if ($off > 0);
406 $files{$file}{cont} .= $buffer;
407 $files{$file}{cont} .= substr($cont,$off+length($buffer),$len-$off-length($buffer)) if ($off+length($buffer) < $len);
409 $files{$file}{size} = length($files{$file}{cont});
411 if (! update_db($file)) {
414 return length($buffer);
419 my $file = filename_fixup(shift);
422 print "truncate to $size\n";
424 $files{$file}{cont} = substr($files{$file}{cont},0,$size);
425 $files{$file}{size} = $size;
431 my ($atime,$mtime,$file) = @_;
432 $file = filename_fixup($file);
434 return -ENOENT() unless exists($files{$file});
436 print "utime '$file' $atime $mtime\n";
438 $files{$file}{time} = $mtime;
442 sub e_statfs { return 255, 1, 1, 1, 1, 2 }
445 my $file = filename_fixup(shift);
447 if (exists( $dirs{$file} )) {
448 print "unlink '$file' will re-read template names\n";
449 print Dumper($fuse_self);
450 $$fuse_self->{'read_filenames'}->();
452 } elsif (exists( $files{$file} )) {
453 print "unlink '$file' will invalidate cache\n";
454 read_content($file,$files{$file}{id});
469 C<FUSE (Filesystem in USErspace)> website
470 L<http://sourceforge.net/projects/avf>
474 Dobrica Pavlinusic, E<lt>dpavlin@rot13.orgE<gt>
476 =head1 COPYRIGHT AND LICENSE
478 Copyright (C) 2004 by Dobrica Pavlinusic
480 This library is free software; you can redistribute it and/or modify
481 it under the same terms as Perl itself, either Perl version 5.8.4 or,
482 at your option, any later version of Perl 5 you may have available.