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 L<run> below for examples how to set parametars.
31 This module will use L<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;
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 $ctime_start = time();
91 die "fork() failed: $!" unless defined $pid;
92 # child will return to caller
98 $dbh = DBI->connect($arg->{'dsn'},$arg->{'user'},$arg->{'password'}, {AutoCommit => 0, RaiseError => 1}) || die $DBI::errstr;
100 $sth->{filenames} = $dbh->prepare($arg->{'filenames'}) || die $dbh->errstr();
102 $sth->{'read'} = $dbh->prepare($arg->{'read'}) || die $dbh->errstr();
103 $sth->{'update'} = $dbh->prepare($arg->{'update'}) || die $dbh->errstr();
105 $self->read_filenames;
108 mountpoint=>$arg->{'mount'},
109 getattr=>\&e_getattr,
116 truncate=>\&e_truncate,
121 exit(0) if ($arg->{'fork'});
129 Unmount your database as filesystem.
133 This will also kill background process which is translating
134 database to filesystem.
141 system "fusermount -u ".$self->{'mount'} || croak "umount error: $!";
146 =head2 fuse_module_loaded
148 Checks if C<fuse> module is loaded in kernel.
150 die "no fuse module loaded in kernel"
151 unless (Fuse::DBI::fuse_module_loaded);
153 This function in called by L<mount>, but might be useful alone also.
157 sub fuse_module_loaded {
159 die "can't start lsmod: $!" unless ($lsmod);
160 if ($lsmod =~ m/fuse/s) {
173 # create empty filesystem
180 # cont => "File 'a'.\n",
182 # ctime => time()-2000
186 # fetch new filename list from database
187 $sth->{'filenames'}->execute() || die $sth->{'filenames'}->errstr();
189 # read them in with sesible defaults
190 while (my $row = $sth->{'filenames'}->fetchrow_hashref() ) {
191 $files{$row->{'filename'}} = {
192 size => $row->{'size'},
193 mode => $row->{'writable'} ? 0644 : 0444,
194 id => $row->{'id'} || 99,
198 foreach (split(m!/!, $row->{'filename'})) {
199 # first, entry is assumed to be file
220 print "found ",scalar(keys %files)-scalar(keys %dirs)," files, ",scalar(keys %dirs), " dirs\n";
227 $file = '.' unless length($file);
232 my ($file) = filename_fixup(shift);
234 $file = '.' unless length($file);
235 return -ENOENT() unless exists($files{$file});
236 my ($size) = $files{$file}{size} || 1;
237 my ($dev, $ino, $rdev, $blocks, $gid, $uid, $nlink, $blksize) = (0,0,0,1,0,0,1,1024);
238 my ($atime, $ctime, $mtime);
239 $atime = $ctime = $mtime = $files{$file}{ctime} || $ctime_start;
241 my ($modes) = (($files{$file}{type} || 0100)<<9) + $files{$file}{mode};
243 # 2 possible types of return values:
244 #return -ENOENT(); # or any other error you care to
245 #print(join(",",($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)),"\n");
246 return ($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks);
250 my ($dirname) = shift;
252 # return as many text filenames as you like, followed by the retval.
253 print((scalar keys %files)." files total\n");
255 foreach my $f (sort keys %files) {
257 if ($f =~ s/^\E$dirname\Q\///) {
258 $out{$f}++ if ($f =~ /^[^\/]+$/);
261 $out{$f}++ if ($f =~ /^[^\/]+$/);
265 $out{'no files? bug?'}++;
267 print scalar keys %out," files in dir '$dirname'\n";
268 print "## ",join(" ",keys %out),"\n";
269 return (keys %out),0;
275 die "read_content needs file and id" unless ($file && $id);
277 $sth->{'read'}->execute($id) || die $sth->{'read'}->errstr;
278 $files{$file}{cont} = $sth->{'read'}->fetchrow_array;
279 print "file '$file' content [",length($files{$file}{cont})," bytes] read in cache\n";
284 # VFS sanity check; it keeps all the necessary state, not much to do here.
285 my $file = filename_fixup(shift);
288 return -ENOENT() unless exists($files{$file});
289 return -EISDIR() unless exists($files{$file}{id});
291 read_content($file,$files{$file}{id}) unless exists($files{$file}{cont});
293 print "open '$file' ",length($files{$file}{cont})," bytes\n";
298 # return an error numeric, or binary/text string.
299 # (note: 0 means EOF, "0" will give a byte (ascii "0")
300 # to the reading program)
301 my ($file) = filename_fixup(shift);
302 my ($buf_len,$off) = @_;
304 return -ENOENT() unless exists($files{$file});
306 my $len = length($files{$file}{cont});
308 print "read '$file' [$len bytes] offset $off length $buf_len\n";
310 return -EINVAL() if ($off > $len);
311 return 0 if ($off == $len);
313 $buf_len = $len-$off if ($len - $off < $buf_len);
315 return substr($files{$file}{cont},$off,$buf_len);
319 print "transaction rollback\n";
320 $dbh->rollback || die $dbh->errstr;
321 print "invalidate all cached content\n";
322 foreach my $f (keys %files) {
323 delete $files{$f}{cont};
325 print "begin new transaction\n";
326 #$dbh->begin_work || die $dbh->errstr;
331 my $file = shift || die;
333 $files{$file}{ctime} = time();
340 if (!$sth->{'update'}->execute($cont,$id)) {
341 print "update problem: ",$sth->{'update'}->errstr;
345 if (! $dbh->commit) {
346 print "ERROR: commit problem: ",$sth->{'update'}->errstr;
350 print "updated '$file' [",$files{$file}{id},"]\n";
356 my $file = filename_fixup(shift);
357 my ($buffer,$off) = @_;
359 return -ENOENT() unless exists($files{$file});
361 my $cont = $files{$file}{cont};
362 my $len = length($cont);
364 print "write '$file' [$len bytes] offset $off length ",length($buffer),"\n";
366 $files{$file}{cont} = "";
368 $files{$file}{cont} .= substr($cont,0,$off) if ($off > 0);
369 $files{$file}{cont} .= $buffer;
370 $files{$file}{cont} .= substr($cont,$off+length($buffer),$len-$off-length($buffer)) if ($off+length($buffer) < $len);
372 $files{$file}{size} = length($files{$file}{cont});
374 if (! update_db($file)) {
377 return length($buffer);
382 my $file = filename_fixup(shift);
385 print "truncate to $size\n";
387 $files{$file}{cont} = substr($files{$file}{cont},0,$size);
388 $files{$file}{size} = $size;
394 my ($atime,$mtime,$file) = @_;
395 $file = filename_fixup($file);
397 return -ENOENT() unless exists($files{$file});
399 print "utime '$file' $atime $mtime\n";
401 $files{$file}{time} = $mtime;
405 sub e_statfs { return 255, 1, 1, 1, 1, 2 }
408 my $file = filename_fixup(shift);
410 return -ENOENT() unless exists($files{$file});
412 print "unlink '$file' will invalidate cache\n";
414 read_content($file,$files{$file}{id});
427 C<FUSE (Filesystem in USErspace)> website
428 L<http://sourceforge.net/projects/avf>
432 Dobrica Pavlinusic, E<lt>dpavlin@rot13.orgE<gt>
434 =head1 COPYRIGHT AND LICENSE
436 Copyright (C) 2004 by Dobrica Pavlinusic
438 This library is free software; you can redistribute it and/or modify
439 it under the same terms as Perl itself, either Perl version 5.8.4 or,
440 at your option, any later version of Perl 5 you may have available.