/[fuse_dbi]/trunk/DBI.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Diff of /trunk/DBI.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 9 by dpavlin, Sat Aug 7 19:06:03 2004 UTC revision 26 by dpavlin, Fri Oct 8 22:55:36 2004 UTC
# Line 9  use warnings; Line 9  use warnings;
9  use POSIX qw(ENOENT EISDIR EINVAL ENOSYS O_RDWR);  use POSIX qw(ENOENT EISDIR EINVAL ENOSYS O_RDWR);
10  use Fuse;  use Fuse;
11  use DBI;  use DBI;
12    use Carp;
13    use Data::Dumper;
14    
15  our $VERSION = '0.01';  
16    our $VERSION = '0.03';
17    
18  =head1 NAME  =head1 NAME
19    
# Line 19  Fuse::DBI - mount your database as files Line 22  Fuse::DBI - mount your database as files
22  =head1 SYNOPSIS  =head1 SYNOPSIS
23    
24    use Fuse::DBI;    use Fuse::DBI;
25    Fuse::DBI->run( ... );    Fuse::DBI->mount( ... );
26    
27  See L<run> below for examples how to set parametars.  See C<run> below for examples how to set parametars.
28    
29  =head1 DESCRIPTION  =head1 DESCRIPTION
30    
31  This module will use L<Fuse> module, part of C<FUSE (Filesystem in USErspace)>  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  available at L<http://sourceforge.net/projects/avf> to mount
33  your database as file system.  your database as file system.
34    
# Line 39  It's actually opposite of Oracle's inten Line 42  It's actually opposite of Oracle's inten
42    
43  =cut  =cut
44    
45  =head2 run  =head2 mount
46    
47  Mount your database as filesystem.  Mount your database as filesystem.
48    
49    Fuse::DBI->run({    my $mnt = Fuse::DBI->mount({
50          filenames => 'select name from filenamefilenames,          filenames => 'select name from files_table as filenames',
51          read => 'sql read',          read => 'sql read',
52          update => 'sql update',          update => 'sql update',
53          dsn => 'DBI:Pg:dbname=webgui',          dsn => 'DBI:Pg:dbname=webgui',
# Line 58  my $dbh; Line 61  my $dbh;
61  my $sth;  my $sth;
62  my $ctime_start;  my $ctime_start;
63    
64  sub run {  sub read_filenames;
65          my $self = shift  sub fuse_module_loaded;
66    
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
69    # be a problem.
70    my $fuse_self;
71    
72    sub mount {
73            my $class = shift;
74            my $self = {};
75            bless($self, $class);
76    
77            my $arg = shift;
78    
79            print Dumper($arg);
80    
81          my $arg = {@_};          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'});
83    
84          carp "run needs 'dsn' to connect to (e.g. dsn => 'DBI:Pg:dbname=test')" unless ($arg->{'dsn'});          # save (some) arguments in self
85          carp "run needs 'mount' as mountpoint" unless ($arg->{'mount'});          foreach (qw(mount invalidate)) {
86                    $self->{$_} = $arg->{$_};
87            }
88    
89          foreach (qw(filenames read update)) {          foreach (qw(filenames read update)) {
90                  carp "run needs '$_' SQL" unless ($arg->{$_});                  carp "mount needs '$_' SQL" unless ($arg->{$_});
91          }          }
92    
93          $dbh = DBI->connect($arg->{'dsn'},$arg->{'user'},$arg->{'password'}, { AutoCommit => 0 }) || die $DBI::errstr;          $ctime_start = time();
94    
95          print "start transaction\n";          my $pid;
96          #$dbh->begin_work || die $dbh->errstr;          if ($arg->{'fork'}) {
97                    $pid = fork();
98                    die "fork() failed: $!" unless defined $pid;
99                    # child will return to caller
100                    if ($pid) {
101                            return $self;
102                    }
103            }
104    
105          $sth->{filenames} = $dbh->prepare($arg->{'filenames'}) || die $dbh->errstr();          $dbh = DBI->connect($arg->{'dsn'},$arg->{'user'},$arg->{'password'}, {AutoCommit => 0, RaiseError => 1}) || die $DBI::errstr;
106    
107            $sth->{'filenames'} = $dbh->prepare($arg->{'filenames'}) || die $dbh->errstr();
108    
109          $sth->{'read'} = $dbh->prepare($arg->{'read'}) || die $dbh->errstr();          $sth->{'read'} = $dbh->prepare($arg->{'read'}) || die $dbh->errstr();
110          $sth->{'update'} = $dbh->prepare($arg->{'update'}) || die $dbh->errstr();          $sth->{'update'} = $dbh->prepare($arg->{'update'}) || die $dbh->errstr();
111    
         $ctime_start = time();  
112    
113          read_filenames;          $self->{'sth'} = $sth;
114    
115            $self->{'read_filenames'} = sub { $self->read_filenames };
116            $self->read_filenames;
117    
118            $self->{'mounted'} = 1;
119    
120            $fuse_self = \$self;
121    
122          Fuse::main(          Fuse::main(
123                  mountpoint=>$arg->{'mount'},                  mountpoint=>$arg->{'mount'},
# Line 94  sub run { Line 129  sub run {
129                  write=>\&e_write,                  write=>\&e_write,
130                  utime=>\&e_utime,                  utime=>\&e_utime,
131                  truncate=>\&e_truncate,                  truncate=>\&e_truncate,
132                    unlink=>\&e_unlink,
133                    rmdir=>\&e_unlink,
134                  debug=>0,                  debug=>0,
135          );          );
136            
137            $self->{'mounted'} = 0;
138    
139            exit(0) if ($arg->{'fork'});
140    
141            return 1;
142    
143  };  };
144    
145    =head2 umount
146    
147    Unmount your database as filesystem.
148    
149      $mnt->umount;
150    
151    This will also kill background process which is translating
152    database to filesystem.
153    
154    =cut
155    
156    sub umount {
157            my $self = shift;
158    
159            if ($self->{'mounted'}) {
160                    system "fusermount -u ".$self->{'mount'} || croak "umount error: $!";
161            }
162    
163            return 1;
164    }
165    
166    $SIG{'INT'} = sub {
167            print STDERR "umount called by SIG INT\n";
168            umount;
169    };
170    
171    sub DESTROY {
172            my $self = shift;
173            return if (! $self->{'mounted'});
174            print STDERR "umount called by DESTROY\n";
175            $self->umount;
176    }
177    
178    =head2 fuse_module_loaded
179    
180    Checks if C<fuse> module is loaded in kernel.
181    
182      die "no fuse module loaded in kernel"
183            unless (Fuse::DBI::fuse_module_loaded);
184    
185    This function in called by L<mount>, but might be useful alone also.
186    
187    =cut
188    
189    sub fuse_module_loaded {
190            my $lsmod = `lsmod`;
191            die "can't start lsmod: $!" unless ($lsmod);
192            if ($lsmod =~ m/fuse/s) {
193                    return 1;
194            } else {
195                    return 0;
196            }
197    }
198    
199  my %files;  my %files;
200  my %dirs;  my %dirs;
201    
202  sub read_filenames {  sub read_filenames {
203            my $self = shift;
204    
205            my $sth = $self->{'sth'} || die "no sth argument";
206    
207          # create empty filesystem          # create empty filesystem
208          (%files) = (          (%files) = (
209                  '.' => {                  '.' => {
# Line 184  sub e_getdir { Line 286  sub e_getdir {
286          # return as many text filenames as you like, followed by the retval.          # return as many text filenames as you like, followed by the retval.
287          print((scalar keys %files)." files total\n");          print((scalar keys %files)." files total\n");
288          my %out;          my %out;
289          foreach (keys %files) {          foreach my $f (sort keys %files) {
                 my $f = $_;  
                 $f =~ s/^\E$dirname\Q//;  
                 $f =~ s/^\///;  
290                  if ($dirname) {                  if ($dirname) {
291                          $out{$f}++ if (/^\E$dirname\Q/ && $f =~ /^[^\/]+$/);                          if ($f =~ s/^\E$dirname\Q\///) {
292                                    $out{$f}++ if ($f =~ /^[^\/]+$/);
293                            }
294                  } else {                  } else {
295                          $out{$f}++ if ($f =~ /^[^\/]+$/);                          $out{$f}++ if ($f =~ /^[^\/]+$/);
296                  }                  }
# Line 198  sub e_getdir { Line 299  sub e_getdir {
299                  $out{'no files? bug?'}++;                  $out{'no files? bug?'}++;
300          }          }
301          print scalar keys %out," files in dir '$dirname'\n";          print scalar keys %out," files in dir '$dirname'\n";
302            print "## ",join(" ",keys %out),"\n";
303          return (keys %out),0;          return (keys %out),0;
304  }  }
305    
306    sub read_content {
307            my ($file,$id) = @_;
308    
309            die "read_content needs file and id" unless ($file && $id);
310    
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";
315    }
316    
317    
318  sub e_open {  sub e_open {
319          # VFS sanity check; it keeps all the necessary state, not much to do here.          # VFS sanity check; it keeps all the necessary state, not much to do here.
320          my $file = filename_fixup(shift);          my $file = filename_fixup(shift);
# Line 209  sub e_open { Line 323  sub e_open {
323          return -ENOENT() unless exists($files{$file});          return -ENOENT() unless exists($files{$file});
324          return -EISDIR() unless exists($files{$file}{id});          return -EISDIR() unless exists($files{$file}{id});
325    
326          if (!exists($files{$file}{cont})) {          read_content($file,$files{$file}{id}) unless exists($files{$file}{cont});
327                  $sth->{'read'}->execute($files{$file}{id}) || die $sth->{'read'}->errstr;  
                 $files{$file}{cont} = $sth->{'read'}->fetchrow_array;  
                 print "file '$file' content read in cache\n";  
         }  
328          print "open '$file' ",length($files{$file}{cont})," bytes\n";          print "open '$file' ",length($files{$file}{cont})," bytes\n";
329          return 0;          return 0;
330  }  }
# Line 234  sub e_read { Line 345  sub e_read {
345          return -EINVAL() if ($off > $len);          return -EINVAL() if ($off > $len);
346          return 0 if ($off == $len);          return 0 if ($off == $len);
347    
348          $buf_len = $buf_len-$off if ($off+$buf_len > $len);          $buf_len = $len-$off if ($len - $off < $buf_len);
349    
350          return substr($files{$file}{cont},$off,$buf_len);          return substr($files{$file}{cont},$off,$buf_len);
351  }  }
# Line 247  sub clear_cont { Line 358  sub clear_cont {
358                  delete $files{$f}{cont};                  delete $files{$f}{cont};
359          }          }
360          print "begin new transaction\n";          print "begin new transaction\n";
361          $dbh->begin_work || die $dbh->errstr;          #$dbh->begin_work || die $dbh->errstr;
362  }  }
363    
364    
# Line 256  sub update_db { Line 367  sub update_db {
367    
368          $files{$file}{ctime} = time();          $files{$file}{ctime} = time();
369    
370          if (!$sth->{'update'}->execute($files{$file}{cont},$files{$file}{id})) {          my ($cont,$id) = (
371                    $files{$file}{cont},
372                    $files{$file}{id}
373            );
374    
375            if (!$sth->{'update'}->execute($cont,$id)) {
376                  print "update problem: ",$sth->{'update'}->errstr;                  print "update problem: ",$sth->{'update'}->errstr;
377                  clear_cont;                  clear_cont;
378                  return 0;                  return 0;
# Line 267  sub update_db { Line 383  sub update_db {
383                          return 0;                          return 0;
384                  }                  }
385                  print "updated '$file' [",$files{$file}{id},"]\n";                  print "updated '$file' [",$files{$file}{id},"]\n";
386    
387                    $$fuse_self->{'invalidate'}->() if (ref $$fuse_self->{'invalidate'});
388          }          }
389          return 1;          return 1;
390  }  }
391    
392  sub e_write {  sub e_write {
393          my $file = filename_fixup(shift);          my $file = filename_fixup(shift);
394          my ($buf_len,$off) = @_;          my ($buffer,$off) = @_;
395    
396          return -ENOENT() unless exists($files{$file});          return -ENOENT() unless exists($files{$file});
397    
398          my $len = length($files{$file}{cont});          my $cont = $files{$file}{cont};
399            my $len = length($cont);
400    
401            print "write '$file' [$len bytes] offset $off length ",length($buffer),"\n";
402    
403          print "write '$file' [$len bytes] offset $off length\n";          $files{$file}{cont} = "";
404    
405          $files{$file}{cont} =          $files{$file}{cont} .= substr($cont,0,$off) if ($off > 0);
406                  substr($files{$file}{cont},0,$off) .          $files{$file}{cont} .= $buffer;
407                  $buf_len .          $files{$file}{cont} .= substr($cont,$off+length($buffer),$len-$off-length($buffer)) if ($off+length($buffer) < $len);
408                  substr($files{$file}{cont},$off+length($buf_len));  
409            $files{$file}{size} = length($files{$file}{cont});
410    
411          if (! update_db($file)) {          if (! update_db($file)) {
412                  return -ENOSYS();                  return -ENOSYS();
413          } else {          } else {
414                  return length($buf_len);                  return length($buffer);
415          }          }
416  }  }
417    
# Line 297  sub e_truncate { Line 419  sub e_truncate {
419          my $file = filename_fixup(shift);          my $file = filename_fixup(shift);
420          my $size = shift;          my $size = shift;
421    
422            print "truncate to $size\n";
423    
424          $files{$file}{cont} = substr($files{$file}{cont},0,$size);          $files{$file}{cont} = substr($files{$file}{cont},0,$size);
425            $files{$file}{size} = $size;
426          return 0          return 0
427  };  };
428    
# Line 316  sub e_utime { Line 441  sub e_utime {
441    
442  sub e_statfs { return 255, 1, 1, 1, 1, 2 }  sub e_statfs { return 255, 1, 1, 1, 1, 2 }
443    
444    sub e_unlink {
445            my $file = filename_fixup(shift);
446    
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'}->();
451                    return 0;
452            } elsif (exists( $files{$file} )) {
453                    print "unlink '$file' will invalidate cache\n";
454                    read_content($file,$files{$file}{id});
455                    return 0;
456            }
457    
458            return -ENOENT();
459    }
460  1;  1;
461  __END__  __END__
462    

Legend:
Removed from v.9  
changed lines
  Added in v.26

  ViewVC Help
Powered by ViewVC 1.1.26