/[fuse_dbi]/fuse-couchdb/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

Annotation of /fuse-couchdb/DBI.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 64 - (hide annotations)
Sun Nov 26 22:29:23 2006 UTC (17 years, 4 months ago) by dpavlin
Original Path: trunk/DBI.pm
File size: 15012 byte(s)
first try at cleaning up the code. sleep after fusermount -u
1 dpavlin 1 #!/usr/bin/perl
2    
3 dpavlin 9 package Fuse::DBI;
4    
5     use 5.008;
6     use strict;
7     use warnings;
8    
9 dpavlin 7 use POSIX qw(ENOENT EISDIR EINVAL ENOSYS O_RDWR);
10 dpavlin 1 use Fuse;
11     use DBI;
12 dpavlin 11 use Carp;
13     use Data::Dumper;
14 dpavlin 1
15 dpavlin 61 our $VERSION = '0.09_1';
16 dpavlin 1
17 dpavlin 52 # block size for this filesystem
18     use constant BLOCK => 1024;
19    
20 dpavlin 9 =head1 NAME
21 dpavlin 1
22 dpavlin 9 Fuse::DBI - mount your database as filesystem and use it
23 dpavlin 1
24 dpavlin 9 =head1 SYNOPSIS
25 dpavlin 6
26 dpavlin 9 use Fuse::DBI;
27 dpavlin 11 Fuse::DBI->mount( ... );
28 dpavlin 1
29 dpavlin 28 See C<run> below for examples how to set parameters.
30 dpavlin 1
31 dpavlin 9 =head1 DESCRIPTION
32 dpavlin 1
33 dpavlin 23 This module will use C<Fuse> module, part of C<FUSE (Filesystem in USErspace)>
34 dpavlin 36 available at L<http://fuse.sourceforge.net/> to mount
35 dpavlin 9 your database as file system.
36 dpavlin 1
37 dpavlin 28 That will give you possibility to use normal file-system tools (cat, grep, vi)
38 dpavlin 9 to manipulate data in database.
39 dpavlin 1
40 dpavlin 9 It's actually opposite of Oracle's intention to put everything into database.
41 dpavlin 1
42    
43 dpavlin 9 =head1 METHODS
44    
45     =cut
46    
47 dpavlin 11 =head2 mount
48 dpavlin 9
49     Mount your database as filesystem.
50    
51 dpavlin 28 Let's suppose that your database have table C<files> with following structure:
52    
53     id: int
54     filename: text
55     size: int
56     content: text
57     writable: boolean
58    
59     Following is example how to mount table like that to C</mnt>:
60    
61 dpavlin 11 my $mnt = Fuse::DBI->mount({
62 dpavlin 28 'filenames' => 'select id,filename,size,writable from files',
63     'read' => 'select content from files where id = ?',
64     'update' => 'update files set content = ? where id = ?',
65     'dsn' => 'DBI:Pg:dbname=test_db',
66     'user' => 'database_user',
67     'password' => 'database_password',
68     'invalidate' => sub { ... },
69 dpavlin 9 });
70    
71 dpavlin 28 Options:
72    
73     =over 5
74    
75     =item filenames
76    
77     SQL query which returns C<id> (unique id for that row), C<filename>,
78     C<size> and C<writable> boolean flag.
79    
80     =item read
81    
82     SQL query which returns only one column with content of file and has
83     placeholder C<?> for C<id>.
84    
85     =item update
86    
87     SQL query with two pace-holders, one for new content and one for C<id>.
88    
89     =item dsn
90    
91     C<DBI> dsn to connect to (contains database driver and name of database).
92    
93     =item user
94    
95     User with which to connect to database
96    
97     =item password
98    
99     Password for connecting to database
100    
101     =item invalidate
102    
103     Optional anonymous code reference which will be executed when data is updated in
104     database. It can be used as hook to delete cache (for example on-disk-cache)
105     which is created from data edited through C<Fuse::DBI>.
106    
107     =item fork
108    
109     Optional flag which forks after mount so that executing script will continue
110     running. Implementation is experimental.
111    
112     =back
113    
114 dpavlin 62 There is also alternative way which can generate C<read> and C<update>
115     queries on the fly:
116    
117     my $mnt = Fuse::DBI->mount({
118     'filenames' => 'select id,filename,size,writable from files',
119     'read' => sub {
120     my ($path,$file) = @_;
121     return( 'select content from files where id = ?', $file->{row}->{id} );
122     },
123     'update' => sub {
124     my ($path,$file) = @_;
125     return( 'update files set content = ? where id = ?', $file->{row}->{id} );
126     },
127     'dsn' => 'DBI:Pg:dbname=test_db',
128     'user' => 'database_user',
129     'password' => 'database_password',
130     'invalidate' => sub { ... },
131     });
132    
133 dpavlin 9 =cut
134    
135     my $dbh;
136     my $sth;
137     my $ctime_start;
138    
139 dpavlin 11 sub read_filenames;
140 dpavlin 21 sub fuse_module_loaded;
141 dpavlin 9
142 dpavlin 24 # evil, evil way to solve this. It makes this module non-reentrant. But, since
143     # fuse calls another copy of this script for each mount anyway, this shouldn't
144     # be a problem.
145     my $fuse_self;
146    
147 dpavlin 64 my $debug = 0;
148    
149 dpavlin 11 sub mount {
150     my $class = shift;
151     my $self = {};
152     bless($self, $class);
153 dpavlin 9
154 dpavlin 11 my $arg = shift;
155 dpavlin 9
156 dpavlin 11 print Dumper($arg);
157    
158 dpavlin 51 unless ($self->fuse_module_loaded) {
159     print STDERR "no fuse module loaded. Trying sudo modprobe fuse!\n";
160     system "sudo modprobe fuse" || die "can't modprobe fuse using sudo!\n";
161     }
162    
163 dpavlin 11 carp "mount needs 'dsn' to connect to (e.g. dsn => 'DBI:Pg:dbname=test')" unless ($arg->{'dsn'});
164     carp "mount needs 'mount' as mountpoint" unless ($arg->{'mount'});
165    
166 dpavlin 12 # save (some) arguments in self
167 dpavlin 24 foreach (qw(mount invalidate)) {
168     $self->{$_} = $arg->{$_};
169     }
170 dpavlin 12
171 dpavlin 9 foreach (qw(filenames read update)) {
172 dpavlin 11 carp "mount needs '$_' SQL" unless ($arg->{$_});
173 dpavlin 9 }
174    
175 dpavlin 21 $ctime_start = time();
176 dpavlin 9
177 dpavlin 22 my $pid;
178 dpavlin 21 if ($arg->{'fork'}) {
179 dpavlin 22 $pid = fork();
180 dpavlin 21 die "fork() failed: $!" unless defined $pid;
181     # child will return to caller
182     if ($pid) {
183 dpavlin 47 my $counter = 4;
184     while ($counter && ! $self->is_mounted) {
185     select(undef, undef, undef, 0.5);
186     $counter--;
187     }
188     if ($self->is_mounted) {
189     return $self;
190     } else {
191     return undef;
192     }
193 dpavlin 21 }
194     }
195 dpavlin 9
196 dpavlin 21 $dbh = DBI->connect($arg->{'dsn'},$arg->{'user'},$arg->{'password'}, {AutoCommit => 0, RaiseError => 1}) || die $DBI::errstr;
197    
198 dpavlin 26 $sth->{'filenames'} = $dbh->prepare($arg->{'filenames'}) || die $dbh->errstr();
199 dpavlin 9
200 dpavlin 26 $self->{'sth'} = $sth;
201 dpavlin 64 $self->{'dbh'} = $dbh;
202 dpavlin 26
203     $self->{'read_filenames'} = sub { $self->read_filenames };
204 dpavlin 21 $self->read_filenames;
205 dpavlin 9
206 dpavlin 62 foreach my $op (qw/read update/) {
207     if (ref($arg->{ $op }) ne 'CODE') {
208     $self->{ $op . '_ref' } = sub {
209     my $row = shift;
210     return ($arg->{ $op }, $row->{'id'});
211     }
212     } else {
213     $self->{ $op . '_ref' } = $arg->{ $op };
214     }
215     }
216    
217 dpavlin 64 $fuse_self = $self;
218 dpavlin 26
219 dpavlin 22 Fuse::main(
220 dpavlin 21 mountpoint=>$arg->{'mount'},
221     getattr=>\&e_getattr,
222     getdir=>\&e_getdir,
223     open=>\&e_open,
224     statfs=>\&e_statfs,
225     read=>\&e_read,
226     write=>\&e_write,
227     utime=>\&e_utime,
228     truncate=>\&e_truncate,
229     unlink=>\&e_unlink,
230 dpavlin 26 rmdir=>\&e_unlink,
231 dpavlin 64 debug=>$debug,
232 dpavlin 21 );
233 dpavlin 26
234 dpavlin 22 exit(0) if ($arg->{'fork'});
235    
236     return 1;
237    
238 dpavlin 9 };
239    
240 dpavlin 47 =head2 is_mounted
241    
242     Check if fuse filesystem is mounted
243    
244     if ($mnt->is_mounted) { ... }
245    
246     =cut
247    
248     sub is_mounted {
249     my $self = shift;
250    
251     my $mounted = 0;
252     my $mount = $self->{'mount'} || confess "can't find mount point!";
253     if (open(MTAB, "/etc/mtab")) {
254     while(<MTAB>) {
255     $mounted = 1 if (/ $mount fuse /i);
256     }
257     close(MTAB);
258     } else {
259     warn "can't open /etc/mtab: $!";
260     }
261    
262     return $mounted;
263     }
264    
265    
266 dpavlin 11 =head2 umount
267    
268     Unmount your database as filesystem.
269    
270     $mnt->umount;
271    
272     This will also kill background process which is translating
273     database to filesystem.
274    
275     =cut
276    
277     sub umount {
278     my $self = shift;
279    
280 dpavlin 47 if ($self->{'mount'} && $self->is_mounted) {
281 dpavlin 53 system "( fusermount -u ".$self->{'mount'}." 2>&1 ) >/dev/null";
282 dpavlin 64 sleep 1;
283 dpavlin 53 if ($self->is_mounted) {
284 dpavlin 51 system "sudo umount ".$self->{'mount'} ||
285     return 0;
286 dpavlin 53 }
287 dpavlin 47 return 1;
288     }
289 dpavlin 40
290 dpavlin 47 return 0;
291 dpavlin 21 }
292    
293 dpavlin 26 $SIG{'INT'} = sub {
294 dpavlin 64 if ($fuse_self && $fuse_self->can('umount')) {
295 dpavlin 40 print STDERR "umount called by SIG INT\n";
296     }
297 dpavlin 26 };
298 dpavlin 24
299 dpavlin 40 $SIG{'QUIT'} = sub {
300 dpavlin 64 if ($fuse_self && $fuse_self->can('umount')) {
301 dpavlin 40 print STDERR "umount called by SIG QUIT\n";
302     }
303     };
304    
305 dpavlin 24 sub DESTROY {
306     my $self = shift;
307 dpavlin 40 if ($self->umount) {
308     print STDERR "umount called by DESTROY\n";
309     }
310 dpavlin 24 }
311    
312 dpavlin 21 =head2 fuse_module_loaded
313    
314     Checks if C<fuse> module is loaded in kernel.
315    
316     die "no fuse module loaded in kernel"
317     unless (Fuse::DBI::fuse_module_loaded);
318    
319 dpavlin 28 This function in called by C<mount>, but might be useful alone also.
320 dpavlin 21
321     =cut
322    
323     sub fuse_module_loaded {
324     my $lsmod = `lsmod`;
325     die "can't start lsmod: $!" unless ($lsmod);
326     if ($lsmod =~ m/fuse/s) {
327     return 1;
328 dpavlin 12 } else {
329 dpavlin 21 return 0;
330 dpavlin 12 }
331 dpavlin 11 }
332    
333 dpavlin 61 my $files;
334 dpavlin 1
335 dpavlin 9 sub read_filenames {
336 dpavlin 11 my $self = shift;
337    
338 dpavlin 26 my $sth = $self->{'sth'} || die "no sth argument";
339    
340 dpavlin 9 # create empty filesystem
341 dpavlin 61 $files = {
342 dpavlin 9 '.' => {
343     type => 0040,
344     mode => 0755,
345     },
346 dpavlin 40 '..' => {
347     type => 0040,
348     mode => 0755,
349     },
350 dpavlin 9 # a => {
351     # cont => "File 'a'.\n",
352     # type => 0100,
353     # ctime => time()-2000
354     # },
355 dpavlin 61 };
356 dpavlin 1
357 dpavlin 9 # fetch new filename list from database
358     $sth->{'filenames'}->execute() || die $sth->{'filenames'}->errstr();
359    
360     # read them in with sesible defaults
361     while (my $row = $sth->{'filenames'}->fetchrow_hashref() ) {
362 dpavlin 55 $row->{'filename'} ||= 'NULL-'.$row->{'id'};
363 dpavlin 61 $files->{$row->{'filename'}} = {
364 dpavlin 9 size => $row->{'size'},
365     mode => $row->{'writable'} ? 0644 : 0444,
366 dpavlin 62 id => $row->{'id'} || undef,
367     row => $row,
368 dpavlin 9 };
369    
370 dpavlin 55
371 dpavlin 9 my $d;
372     foreach (split(m!/!, $row->{'filename'})) {
373     # first, entry is assumed to be file
374     if ($d) {
375 dpavlin 61 $files->{$d} = {
376 dpavlin 9 mode => 0755,
377     type => 0040
378     };
379 dpavlin 61 $files->{$d.'/.'} = {
380 dpavlin 9 mode => 0755,
381     type => 0040
382     };
383 dpavlin 61 $files->{$d.'/..'} = {
384 dpavlin 9 mode => 0755,
385     type => 0040
386     };
387     }
388     $d .= "/" if ($d);
389     $d .= "$_";
390 dpavlin 1 }
391     }
392 dpavlin 9
393 dpavlin 61 print "found ",scalar(keys %{$files})," files\n";
394 dpavlin 1 }
395    
396    
397     sub filename_fixup {
398     my ($file) = shift;
399     $file =~ s,^/,,;
400     $file = '.' unless length($file);
401     return $file;
402     }
403    
404     sub e_getattr {
405     my ($file) = filename_fixup(shift);
406     $file =~ s,^/,,;
407     $file = '.' unless length($file);
408 dpavlin 61 return -ENOENT() unless exists($files->{$file});
409     my ($size) = $files->{$file}->{size} || 0;
410 dpavlin 52 my ($dev, $ino, $rdev, $blocks, $gid, $uid, $nlink, $blksize) = (0,0,0,int(($size+BLOCK-1)/BLOCK),0,0,1,BLOCK);
411 dpavlin 1 my ($atime, $ctime, $mtime);
412 dpavlin 61 $atime = $ctime = $mtime = $files->{$file}->{ctime} || $ctime_start;
413 dpavlin 1
414 dpavlin 61 my ($modes) = (($files->{$file}->{type} || 0100)<<9) + $files->{$file}->{mode};
415 dpavlin 1
416     # 2 possible types of return values:
417     #return -ENOENT(); # or any other error you care to
418 dpavlin 53 #print "getattr($file) ",join(",",($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)),"\n";
419 dpavlin 1 return ($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks);
420     }
421    
422     sub e_getdir {
423     my ($dirname) = shift;
424     $dirname =~ s!^/!!;
425     # return as many text filenames as you like, followed by the retval.
426 dpavlin 61 print((scalar keys %{$files})." files total\n");
427 dpavlin 1 my %out;
428 dpavlin 61 foreach my $f (sort keys %{$files}) {
429 dpavlin 1 if ($dirname) {
430 dpavlin 32 if ($f =~ s/^\Q$dirname\E\///) {
431 dpavlin 13 $out{$f}++ if ($f =~ /^[^\/]+$/);
432     }
433 dpavlin 1 } else {
434     $out{$f}++ if ($f =~ /^[^\/]+$/);
435     }
436     }
437     if (! %out) {
438     $out{'no files? bug?'}++;
439     }
440 dpavlin 8 print scalar keys %out," files in dir '$dirname'\n";
441 dpavlin 13 print "## ",join(" ",keys %out),"\n";
442 dpavlin 1 return (keys %out),0;
443     }
444    
445 dpavlin 21 sub read_content {
446 dpavlin 62 my $file = shift || die "need file";
447 dpavlin 21
448 dpavlin 62 warn "file: $file\n", Dumper($fuse_self);
449 dpavlin 21
450 dpavlin 64 my @args = $fuse_self->{'read_ref'}->($files->{$file});
451 dpavlin 62 my $sql = shift @args || die "need SQL for $file";
452    
453 dpavlin 64 $fuse_self->{'read_sth'}->{$sql} ||= $fuse_self->{dbh}->prepare($sql) || die $dbh->errstr();
454     my $sth = $fuse_self->{'read_sth'}->{$sql} || die;
455 dpavlin 62
456     $sth->execute(@args) || die $sth->errstr;
457     $files->{$file}->{cont} = $sth->fetchrow_array;
458 dpavlin 31 # I should modify ctime only if content in database changed
459 dpavlin 61 #$files->{$file}->{ctime} = time() unless ($files->{$file}->{ctime});
460     print "file '$file' content [",length($files->{$file}->{cont})," bytes] read in cache\n";
461 dpavlin 21 }
462    
463    
464 dpavlin 1 sub e_open {
465     # VFS sanity check; it keeps all the necessary state, not much to do here.
466 dpavlin 6 my $file = filename_fixup(shift);
467     my $flags = shift;
468    
469 dpavlin 61 return -ENOENT() unless exists($files->{$file});
470     return -EISDIR() unless exists($files->{$file}->{id});
471 dpavlin 6
472 dpavlin 61 read_content($file,$files->{$file}->{id}) unless exists($files->{$file}->{cont});
473 dpavlin 21
474 dpavlin 61 $files->{$file}->{cont} ||= '';
475     print "open '$file' ",length($files->{$file}->{cont})," bytes\n";
476 dpavlin 1 return 0;
477     }
478    
479     sub e_read {
480 dpavlin 3 # return an error numeric, or binary/text string.
481     # (note: 0 means EOF, "0" will give a byte (ascii "0")
482     # to the reading program)
483 dpavlin 1 my ($file) = filename_fixup(shift);
484 dpavlin 8 my ($buf_len,$off) = @_;
485 dpavlin 3
486 dpavlin 61 return -ENOENT() unless exists($files->{$file});
487 dpavlin 3
488 dpavlin 61 my $len = length($files->{$file}->{cont});
489 dpavlin 3
490 dpavlin 8 print "read '$file' [$len bytes] offset $off length $buf_len\n";
491 dpavlin 3
492     return -EINVAL() if ($off > $len);
493     return 0 if ($off == $len);
494    
495 dpavlin 21 $buf_len = $len-$off if ($len - $off < $buf_len);
496 dpavlin 3
497 dpavlin 61 return substr($files->{$file}->{cont},$off,$buf_len);
498 dpavlin 1 }
499    
500 dpavlin 6 sub clear_cont {
501 dpavlin 7 print "transaction rollback\n";
502     $dbh->rollback || die $dbh->errstr;
503 dpavlin 6 print "invalidate all cached content\n";
504 dpavlin 61 foreach my $f (keys %{$files}) {
505     delete $files->{$f}->{cont};
506     delete $files->{$f}->{ctime};
507 dpavlin 6 }
508 dpavlin 7 print "begin new transaction\n";
509 dpavlin 21 #$dbh->begin_work || die $dbh->errstr;
510 dpavlin 6 }
511    
512    
513     sub update_db {
514 dpavlin 62 my $file = shift || die "need file";
515 dpavlin 6
516 dpavlin 61 $files->{$file}->{ctime} = time();
517 dpavlin 8
518 dpavlin 21 my ($cont,$id) = (
519 dpavlin 61 $files->{$file}->{cont},
520     $files->{$file}->{id}
521 dpavlin 21 );
522    
523 dpavlin 64 my @args = $fuse_self->{'update_ref'}->($files->{$file});
524    
525 dpavlin 62 my $sql = shift @args || die "need SQL for $file";
526    
527 dpavlin 64 unshift @args, $files->{$file}->{cont} if ($#args == 0);
528    
529     warn "## SQL: $sql\n# files->{$file} = ", Dumper($files->{$file}), $/ if ($debug);
530    
531     my $sth = $fuse_self->{'update_sth'}->{$sql}
532     ||= $fuse_self->{dbh}->prepare($sql)
533 dpavlin 62 || die $dbh->errstr();
534    
535     if (!$sth->execute(@args)) {
536     print "update problem: ",$sth->errstr;
537 dpavlin 6 clear_cont;
538     return 0;
539     } else {
540 dpavlin 7 if (! $dbh->commit) {
541 dpavlin 62 print "ERROR: commit problem: ",$sth->errstr;
542 dpavlin 6 clear_cont;
543     return 0;
544     }
545 dpavlin 61 print "updated '$file' [",$files->{$file}->{id},"]\n";
546 dpavlin 24
547 dpavlin 64 $fuse_self->{'invalidate'}->() if ($fuse_self->can('invalidate'));
548 dpavlin 6 }
549     return 1;
550     }
551    
552     sub e_write {
553     my $file = filename_fixup(shift);
554 dpavlin 18 my ($buffer,$off) = @_;
555 dpavlin 6
556 dpavlin 61 return -ENOENT() unless exists($files->{$file});
557 dpavlin 6
558 dpavlin 61 my $cont = $files->{$file}->{cont};
559 dpavlin 18 my $len = length($cont);
560 dpavlin 6
561 dpavlin 18 print "write '$file' [$len bytes] offset $off length ",length($buffer),"\n";
562 dpavlin 6
563 dpavlin 61 $files->{$file}->{cont} = "";
564 dpavlin 6
565 dpavlin 61 $files->{$file}->{cont} .= substr($cont,0,$off) if ($off > 0);
566     $files->{$file}->{cont} .= $buffer;
567     $files->{$file}->{cont} .= substr($cont,$off+length($buffer),$len-$off-length($buffer)) if ($off+length($buffer) < $len);
568 dpavlin 18
569 dpavlin 61 $files->{$file}->{size} = length($files->{$file}->{cont});
570 dpavlin 18
571 dpavlin 6 if (! update_db($file)) {
572     return -ENOSYS();
573     } else {
574 dpavlin 18 return length($buffer);
575 dpavlin 6 }
576     }
577    
578     sub e_truncate {
579     my $file = filename_fixup(shift);
580     my $size = shift;
581    
582 dpavlin 18 print "truncate to $size\n";
583    
584 dpavlin 61 $files->{$file}->{cont} = substr($files->{$file}->{cont},0,$size);
585     $files->{$file}->{size} = $size;
586 dpavlin 6 return 0
587     };
588    
589    
590     sub e_utime {
591     my ($atime,$mtime,$file) = @_;
592     $file = filename_fixup($file);
593    
594 dpavlin 61 return -ENOENT() unless exists($files->{$file});
595 dpavlin 6
596 dpavlin 8 print "utime '$file' $atime $mtime\n";
597    
598 dpavlin 61 $files->{$file}->{time} = $mtime;
599 dpavlin 6 return 0;
600     }
601    
602 dpavlin 51 sub e_statfs {
603 dpavlin 1
604 dpavlin 51 my $size = 0;
605     my $inodes = 0;
606    
607 dpavlin 61 foreach my $f (keys %{$files}) {
608 dpavlin 51 if ($f !~ /(^|\/)\.\.?$/) {
609 dpavlin 61 $size += $files->{$f}->{size} || 0;
610 dpavlin 51 $inodes++;
611     }
612     print "$inodes: $f [$size]\n";
613     }
614    
615 dpavlin 52 $size = int(($size+BLOCK-1)/BLOCK);
616 dpavlin 51
617 dpavlin 52 my @ret = (255, $inodes, 1, $size, $size-1, BLOCK);
618 dpavlin 51
619 dpavlin 53 #print "statfs: ",join(",",@ret),"\n";
620 dpavlin 51
621     return @ret;
622     }
623    
624 dpavlin 21 sub e_unlink {
625     my $file = filename_fixup(shift);
626    
627 dpavlin 51 # if (exists( $dirs{$file} )) {
628     # print "unlink '$file' will re-read template names\n";
629     # print Dumper($fuse_self);
630 dpavlin 64 # $fuse_self->{'read_filenames'}->();
631 dpavlin 51 # return 0;
632 dpavlin 61 if (exists( $files->{$file} )) {
633 dpavlin 26 print "unlink '$file' will invalidate cache\n";
634 dpavlin 61 read_content($file,$files->{$file}->{id});
635 dpavlin 26 return 0;
636     }
637 dpavlin 21
638 dpavlin 26 return -ENOENT();
639 dpavlin 21 }
640 dpavlin 9 1;
641     __END__
642    
643     =head1 EXPORT
644    
645     Nothing.
646    
647 dpavlin 40 =head1 BUGS
648    
649     Size information (C<ls -s>) is wrong. It's a problem in upstream Fuse module
650     (for which I'm to blame lately), so when it gets fixes, C<Fuse::DBI> will
651     automagically pick it up.
652    
653 dpavlin 9 =head1 SEE ALSO
654    
655     C<FUSE (Filesystem in USErspace)> website
656 dpavlin 36 L<http://fuse.sourceforge.net/>
657 dpavlin 9
658 dpavlin 28 Example for WebGUI which comes with this distribution in
659 dpavlin 30 directory C<examples/webgui.pl>. It also contains a lot of documentation
660 dpavlin 28 about design of this module, usage and limitations.
661    
662 dpavlin 9 =head1 AUTHOR
663    
664     Dobrica Pavlinusic, E<lt>dpavlin@rot13.orgE<gt>
665    
666     =head1 COPYRIGHT AND LICENSE
667    
668     Copyright (C) 2004 by Dobrica Pavlinusic
669    
670     This library is free software; you can redistribute it and/or modify
671     it under the same terms as Perl itself, either Perl version 5.8.4 or,
672     at your option, any later version of Perl 5 you may have available.
673    
674    
675     =cut
676    

Properties

Name Value
svn:executable

  ViewVC Help
Powered by ViewVC 1.1.26