9 use POSIX qw(ENOENT EISDIR EINVAL ENOSYS O_RDWR);
17 our $VERSION = '0.01';
21 Fuse::DBI - mount your database as filesystem and use it
26 Fuse::DBI->mount( ... );
28 See L<run> below for examples how to set parametars.
32 This module will use L<Fuse> module, part of C<FUSE (Filesystem in USErspace)>
33 available at L<http://sourceforge.net/projects/avf> to mount
34 your database as file system.
36 That will give you posibility to use normal file-system tools (cat, grep, vi)
37 to manipulate data in database.
39 It's actually opposite of Oracle's intention to put everything into database.
48 Mount your database as filesystem.
50 my $mnt = Fuse::DBI->mount({
51 filenames => 'select name from filenamefilenames,
53 update => 'sql update',
54 dsn => 'DBI:Pg:dbname=webgui',
55 user => 'database_user',
56 password => 'database_password'
76 carp "mount needs 'dsn' to connect to (e.g. dsn => 'DBI:Pg:dbname=test')" unless ($arg->{'dsn'});
77 carp "mount needs 'mount' as mountpoint" unless ($arg->{'mount'});
79 # save (some) arguments in self
80 $self->{$_} = $arg->{$_} foreach (qw(mount));
82 foreach (qw(filenames read update)) {
83 carp "mount needs '$_' SQL" unless ($arg->{$_});
86 $dbh = DBI->connect($arg->{'dsn'},$arg->{'user'},$arg->{'password'}, { AutoCommit => 0 }) || die $DBI::errstr;
88 print "start transaction\n";
89 #$dbh->begin_work || die $dbh->errstr;
91 $sth->{filenames} = $dbh->prepare($arg->{'filenames'}) || die $dbh->errstr();
93 $sth->{'read'} = $dbh->prepare($arg->{'read'}) || die $dbh->errstr();
94 $sth->{'update'} = $dbh->prepare($arg->{'update'}) || die $dbh->errstr();
96 $ctime_start = time();
100 $self->{'proc'} = Proc::Simple->new();
101 $self->{'proc'}->kill_on_destroy(1);
103 $self->{'proc'}->start( sub {
105 mountpoint=>$arg->{'mount'},
106 getattr=>\&e_getattr,
113 truncate=>\&e_truncate,
118 confess "Fuse::main failed" if (! $self->{'proc'}->poll);
120 $self ? return $self : return undef;
125 Unmount your database as filesystem.
129 This will also kill background process which is translating
130 database to filesystem.
137 confess "no process running?" unless ($self->{'proc'});
139 system "fusermount -u ".$self->{'mount'} || croak "umount error: $!";
141 if ($self->{'proc'}->poll) {
142 $self->{'proc'}->kill;
143 return 1 if (! $self->{'proc'}->poll);
156 # create empty filesystem
163 # cont => "File 'a'.\n",
165 # ctime => time()-2000
169 # fetch new filename list from database
170 $sth->{'filenames'}->execute() || die $sth->{'filenames'}->errstr();
172 # read them in with sesible defaults
173 while (my $row = $sth->{'filenames'}->fetchrow_hashref() ) {
174 $files{$row->{'filename'}} = {
175 size => $row->{'size'},
176 mode => $row->{'writable'} ? 0644 : 0444,
177 id => $row->{'id'} || 99,
181 foreach (split(m!/!, $row->{'filename'})) {
182 # first, entry is assumed to be file
203 print "found ",scalar(keys %files)-scalar(keys %dirs)," files, ",scalar(keys %dirs), " dirs\n";
210 $file = '.' unless length($file);
215 my ($file) = filename_fixup(shift);
217 $file = '.' unless length($file);
218 return -ENOENT() unless exists($files{$file});
219 my ($size) = $files{$file}{size} || 1;
220 my ($dev, $ino, $rdev, $blocks, $gid, $uid, $nlink, $blksize) = (0,0,0,1,0,0,1,1024);
221 my ($atime, $ctime, $mtime);
222 $atime = $ctime = $mtime = $files{$file}{ctime} || $ctime_start;
224 my ($modes) = (($files{$file}{type} || 0100)<<9) + $files{$file}{mode};
226 # 2 possible types of return values:
227 #return -ENOENT(); # or any other error you care to
228 #print(join(",",($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)),"\n");
229 return ($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks);
233 my ($dirname) = shift;
235 # return as many text filenames as you like, followed by the retval.
236 print((scalar keys %files)." files total\n");
238 foreach (keys %files) {
240 $f =~ s/^\E$dirname\Q//;
243 $out{$f}++ if (/^\E$dirname\Q/ && $f =~ /^[^\/]+$/);
245 $out{$f}++ if ($f =~ /^[^\/]+$/);
249 $out{'no files? bug?'}++;
251 print scalar keys %out," files in dir '$dirname'\n";
252 return (keys %out),0;
256 # VFS sanity check; it keeps all the necessary state, not much to do here.
257 my $file = filename_fixup(shift);
260 return -ENOENT() unless exists($files{$file});
261 return -EISDIR() unless exists($files{$file}{id});
263 if (!exists($files{$file}{cont})) {
264 $sth->{'read'}->execute($files{$file}{id}) || die $sth->{'read'}->errstr;
265 $files{$file}{cont} = $sth->{'read'}->fetchrow_array;
266 print "file '$file' content read in cache\n";
268 print "open '$file' ",length($files{$file}{cont})," bytes\n";
273 # return an error numeric, or binary/text string.
274 # (note: 0 means EOF, "0" will give a byte (ascii "0")
275 # to the reading program)
276 my ($file) = filename_fixup(shift);
277 my ($buf_len,$off) = @_;
279 return -ENOENT() unless exists($files{$file});
281 my $len = length($files{$file}{cont});
283 print "read '$file' [$len bytes] offset $off length $buf_len\n";
285 return -EINVAL() if ($off > $len);
286 return 0 if ($off == $len);
288 $buf_len = $buf_len-$off if ($off+$buf_len > $len);
290 return substr($files{$file}{cont},$off,$buf_len);
294 print "transaction rollback\n";
295 $dbh->rollback || die $dbh->errstr;
296 print "invalidate all cached content\n";
297 foreach my $f (keys %files) {
298 delete $files{$f}{cont};
300 print "begin new transaction\n";
301 $dbh->begin_work || die $dbh->errstr;
306 my $file = shift || die;
308 $files{$file}{ctime} = time();
310 if (!$sth->{'update'}->execute($files{$file}{cont},$files{$file}{id})) {
311 print "update problem: ",$sth->{'update'}->errstr;
315 if (! $dbh->commit) {
316 print "ERROR: commit problem: ",$sth->{'update'}->errstr;
320 print "updated '$file' [",$files{$file}{id},"]\n";
326 my $file = filename_fixup(shift);
327 my ($buf_len,$off) = @_;
329 return -ENOENT() unless exists($files{$file});
331 my $len = length($files{$file}{cont});
333 print "write '$file' [$len bytes] offset $off length\n";
335 $files{$file}{cont} =
336 substr($files{$file}{cont},0,$off) .
338 substr($files{$file}{cont},$off+length($buf_len));
340 if (! update_db($file)) {
343 return length($buf_len);
348 my $file = filename_fixup(shift);
351 $files{$file}{cont} = substr($files{$file}{cont},0,$size);
357 my ($atime,$mtime,$file) = @_;
358 $file = filename_fixup($file);
360 return -ENOENT() unless exists($files{$file});
362 print "utime '$file' $atime $mtime\n";
364 $files{$file}{time} = $mtime;
368 sub e_statfs { return 255, 1, 1, 1, 1, 2 }
379 C<FUSE (Filesystem in USErspace)> website
380 L<http://sourceforge.net/projects/avf>
384 Dobrica Pavlinusic, E<lt>dpavlin@rot13.orgE<gt>
386 =head1 COPYRIGHT AND LICENSE
388 Copyright (C) 2004 by Dobrica Pavlinusic
390 This library is free software; you can redistribute it and/or modify
391 it under the same terms as Perl itself, either Perl version 5.8.4 or,
392 at your option, any later version of Perl 5 you may have available.