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->{$_};
87 $fuse_self->{$_} = $arg->{$_};
90 foreach (qw(filenames read update)) {
91 carp "mount needs '$_' SQL" unless ($arg->{$_});
94 $ctime_start = time();
99 die "fork() failed: $!" unless defined $pid;
100 # child will return to caller
106 $dbh = DBI->connect($arg->{'dsn'},$arg->{'user'},$arg->{'password'}, {AutoCommit => 0, RaiseError => 1}) || die $DBI::errstr;
108 $sth->{filenames} = $dbh->prepare($arg->{'filenames'}) || die $dbh->errstr();
110 $sth->{'read'} = $dbh->prepare($arg->{'read'}) || die $dbh->errstr();
111 $sth->{'update'} = $dbh->prepare($arg->{'update'}) || die $dbh->errstr();
113 $self->read_filenames;
116 mountpoint=>$arg->{'mount'},
117 getattr=>\&e_getattr,
124 truncate=>\&e_truncate,
129 exit(0) if ($arg->{'fork'});
137 Unmount your database as filesystem.
141 This will also kill background process which is translating
142 database to filesystem.
149 system "fusermount -u ".$self->{'mount'} || croak "umount error: $!";
155 # print STDERR "umount called by SIG INT\n";
161 print STDERR "umount called by DESTROY\n";
165 =head2 fuse_module_loaded
167 Checks if C<fuse> module is loaded in kernel.
169 die "no fuse module loaded in kernel"
170 unless (Fuse::DBI::fuse_module_loaded);
172 This function in called by L<mount>, but might be useful alone also.
176 sub fuse_module_loaded {
178 die "can't start lsmod: $!" unless ($lsmod);
179 if ($lsmod =~ m/fuse/s) {
192 # create empty filesystem
199 # cont => "File 'a'.\n",
201 # ctime => time()-2000
205 # fetch new filename list from database
206 $sth->{'filenames'}->execute() || die $sth->{'filenames'}->errstr();
208 # read them in with sesible defaults
209 while (my $row = $sth->{'filenames'}->fetchrow_hashref() ) {
210 $files{$row->{'filename'}} = {
211 size => $row->{'size'},
212 mode => $row->{'writable'} ? 0644 : 0444,
213 id => $row->{'id'} || 99,
217 foreach (split(m!/!, $row->{'filename'})) {
218 # first, entry is assumed to be file
239 print "found ",scalar(keys %files)-scalar(keys %dirs)," files, ",scalar(keys %dirs), " dirs\n";
246 $file = '.' unless length($file);
251 my ($file) = filename_fixup(shift);
253 $file = '.' unless length($file);
254 return -ENOENT() unless exists($files{$file});
255 my ($size) = $files{$file}{size} || 1;
256 my ($dev, $ino, $rdev, $blocks, $gid, $uid, $nlink, $blksize) = (0,0,0,1,0,0,1,1024);
257 my ($atime, $ctime, $mtime);
258 $atime = $ctime = $mtime = $files{$file}{ctime} || $ctime_start;
260 my ($modes) = (($files{$file}{type} || 0100)<<9) + $files{$file}{mode};
262 # 2 possible types of return values:
263 #return -ENOENT(); # or any other error you care to
264 #print(join(",",($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)),"\n");
265 return ($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks);
269 my ($dirname) = shift;
271 # return as many text filenames as you like, followed by the retval.
272 print((scalar keys %files)." files total\n");
274 foreach my $f (sort keys %files) {
276 if ($f =~ s/^\E$dirname\Q\///) {
277 $out{$f}++ if ($f =~ /^[^\/]+$/);
280 $out{$f}++ if ($f =~ /^[^\/]+$/);
284 $out{'no files? bug?'}++;
286 print scalar keys %out," files in dir '$dirname'\n";
287 print "## ",join(" ",keys %out),"\n";
288 return (keys %out),0;
294 die "read_content needs file and id" unless ($file && $id);
296 $sth->{'read'}->execute($id) || die $sth->{'read'}->errstr;
297 $files{$file}{cont} = $sth->{'read'}->fetchrow_array;
298 print "file '$file' content [",length($files{$file}{cont})," bytes] read in cache\n";
303 # VFS sanity check; it keeps all the necessary state, not much to do here.
304 my $file = filename_fixup(shift);
307 return -ENOENT() unless exists($files{$file});
308 return -EISDIR() unless exists($files{$file}{id});
310 read_content($file,$files{$file}{id}) unless exists($files{$file}{cont});
312 print "open '$file' ",length($files{$file}{cont})," bytes\n";
317 # return an error numeric, or binary/text string.
318 # (note: 0 means EOF, "0" will give a byte (ascii "0")
319 # to the reading program)
320 my ($file) = filename_fixup(shift);
321 my ($buf_len,$off) = @_;
323 return -ENOENT() unless exists($files{$file});
325 my $len = length($files{$file}{cont});
327 print "read '$file' [$len bytes] offset $off length $buf_len\n";
329 return -EINVAL() if ($off > $len);
330 return 0 if ($off == $len);
332 $buf_len = $len-$off if ($len - $off < $buf_len);
334 return substr($files{$file}{cont},$off,$buf_len);
338 print "transaction rollback\n";
339 $dbh->rollback || die $dbh->errstr;
340 print "invalidate all cached content\n";
341 foreach my $f (keys %files) {
342 delete $files{$f}{cont};
344 print "begin new transaction\n";
345 #$dbh->begin_work || die $dbh->errstr;
350 my $file = shift || die;
352 $files{$file}{ctime} = time();
359 if (!$sth->{'update'}->execute($cont,$id)) {
360 print "update problem: ",$sth->{'update'}->errstr;
364 if (! $dbh->commit) {
365 print "ERROR: commit problem: ",$sth->{'update'}->errstr;
369 print "updated '$file' [",$files{$file}{id},"]\n";
371 $fuse_self->{'invalidate'}->() if (ref $fuse_self->{'invalidate'});
377 my $file = filename_fixup(shift);
378 my ($buffer,$off) = @_;
380 return -ENOENT() unless exists($files{$file});
382 my $cont = $files{$file}{cont};
383 my $len = length($cont);
385 print "write '$file' [$len bytes] offset $off length ",length($buffer),"\n";
387 $files{$file}{cont} = "";
389 $files{$file}{cont} .= substr($cont,0,$off) if ($off > 0);
390 $files{$file}{cont} .= $buffer;
391 $files{$file}{cont} .= substr($cont,$off+length($buffer),$len-$off-length($buffer)) if ($off+length($buffer) < $len);
393 $files{$file}{size} = length($files{$file}{cont});
395 if (! update_db($file)) {
398 return length($buffer);
403 my $file = filename_fixup(shift);
406 print "truncate to $size\n";
408 $files{$file}{cont} = substr($files{$file}{cont},0,$size);
409 $files{$file}{size} = $size;
415 my ($atime,$mtime,$file) = @_;
416 $file = filename_fixup($file);
418 return -ENOENT() unless exists($files{$file});
420 print "utime '$file' $atime $mtime\n";
422 $files{$file}{time} = $mtime;
426 sub e_statfs { return 255, 1, 1, 1, 1, 2 }
429 my $file = filename_fixup(shift);
431 return -ENOENT() unless exists($files{$file});
433 print "unlink '$file' will invalidate cache\n";
435 read_content($file,$files{$file}{id});
448 C<FUSE (Filesystem in USErspace)> website
449 L<http://sourceforge.net/projects/avf>
453 Dobrica Pavlinusic, E<lt>dpavlin@rot13.orgE<gt>
455 =head1 COPYRIGHT AND LICENSE
457 Copyright (C) 2004 by Dobrica Pavlinusic
459 This library is free software; you can redistribute it and/or modify
460 it under the same terms as Perl itself, either Perl version 5.8.4 or,
461 at your option, any later version of Perl 5 you may have available.