9 use POSIX qw(ENOENT EISDIR EINVAL ENOSYS O_RDWR);
17 our $VERSION = '0.02';
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 my $f (sort keys %files) {
240 if ($f =~ s/^\E$dirname\Q\///) {
241 $out{$f}++ if ($f =~ /^[^\/]+$/);
244 $out{$f}++ if ($f =~ /^[^\/]+$/);
248 $out{'no files? bug?'}++;
250 print scalar keys %out," files in dir '$dirname'\n";
251 print "## ",join(" ",keys %out),"\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 ($buffer,$off) = @_;
329 return -ENOENT() unless exists($files{$file});
331 my $cont = $files{$file}{cont};
332 my $len = length($cont);
334 print "write '$file' [$len bytes] offset $off length ",length($buffer),"\n";
336 $files{$file}{cont} = "";
338 $files{$file}{cont} .= substr($cont,0,$off) if ($off > 0);
339 $files{$file}{cont} .= $buffer;
340 $files{$file}{cont} .= substr($cont,-($off+length($buffer))) if ($off+length($buffer) > $len);
342 $files{$file}{size} = length($files{$file}{cont});
344 if (! update_db($file)) {
347 return length($buffer);
352 my $file = filename_fixup(shift);
355 print "truncate to $size\n";
357 $files{$file}{cont} = substr($files{$file}{cont},0,$size);
358 $files{$file}{size} = $size;
364 my ($atime,$mtime,$file) = @_;
365 $file = filename_fixup($file);
367 return -ENOENT() unless exists($files{$file});
369 print "utime '$file' $atime $mtime\n";
371 $files{$file}{time} = $mtime;
375 sub e_statfs { return 255, 1, 1, 1, 1, 2 }
386 C<FUSE (Filesystem in USErspace)> website
387 L<http://sourceforge.net/projects/avf>
391 Dobrica Pavlinusic, E<lt>dpavlin@rot13.orgE<gt>
393 =head1 COPYRIGHT AND LICENSE
395 Copyright (C) 2004 by Dobrica Pavlinusic
397 This library is free software; you can redistribute it and/or modify
398 it under the same terms as Perl itself, either Perl version 5.8.4 or,
399 at your option, any later version of Perl 5 you may have available.