/[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

Contents of /fuse-couchdb/DBI.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 26 - (show annotations)
Fri Oct 8 22:55:36 2004 UTC (19 years, 5 months ago) by dpavlin
Original Path: trunk/DBI.pm
File size: 10412 byte(s)
added invalidation of file list with rmdir,
prevent multiple umounts by keeping mounted flag

1 #!/usr/bin/perl
2
3 package Fuse::DBI;
4
5 use 5.008;
6 use strict;
7 use warnings;
8
9 use POSIX qw(ENOENT EISDIR EINVAL ENOSYS O_RDWR);
10 use Fuse;
11 use DBI;
12 use Carp;
13 use Data::Dumper;
14
15
16 our $VERSION = '0.03';
17
18 =head1 NAME
19
20 Fuse::DBI - mount your database as filesystem and use it
21
22 =head1 SYNOPSIS
23
24 use Fuse::DBI;
25 Fuse::DBI->mount( ... );
26
27 See C<run> below for examples how to set parametars.
28
29 =head1 DESCRIPTION
30
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.
34
35 That will give you posibility to use normal file-system tools (cat, grep, vi)
36 to manipulate data in database.
37
38 It's actually opposite of Oracle's intention to put everything into database.
39
40
41 =head1 METHODS
42
43 =cut
44
45 =head2 mount
46
47 Mount your database as filesystem.
48
49 my $mnt = Fuse::DBI->mount({
50 filenames => 'select name from files_table as filenames',
51 read => 'sql read',
52 update => 'sql update',
53 dsn => 'DBI:Pg:dbname=webgui',
54 user => 'database_user',
55 password => 'database_password'
56 });
57
58 =cut
59
60 my $dbh;
61 my $sth;
62 my $ctime_start;
63
64 sub read_filenames;
65 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 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 # save (some) arguments in self
85 foreach (qw(mount invalidate)) {
86 $self->{$_} = $arg->{$_};
87 }
88
89 foreach (qw(filenames read update)) {
90 carp "mount needs '$_' SQL" unless ($arg->{$_});
91 }
92
93 $ctime_start = time();
94
95 my $pid;
96 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 $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();
110 $sth->{'update'} = $dbh->prepare($arg->{'update'}) || die $dbh->errstr();
111
112
113 $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(
123 mountpoint=>$arg->{'mount'},
124 getattr=>\&e_getattr,
125 getdir=>\&e_getdir,
126 open=>\&e_open,
127 statfs=>\&e_statfs,
128 read=>\&e_read,
129 write=>\&e_write,
130 utime=>\&e_utime,
131 truncate=>\&e_truncate,
132 unlink=>\&e_unlink,
133 rmdir=>\&e_unlink,
134 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;
200 my %dirs;
201
202 sub read_filenames {
203 my $self = shift;
204
205 my $sth = $self->{'sth'} || die "no sth argument";
206
207 # create empty filesystem
208 (%files) = (
209 '.' => {
210 type => 0040,
211 mode => 0755,
212 },
213 # a => {
214 # cont => "File 'a'.\n",
215 # type => 0100,
216 # ctime => time()-2000
217 # },
218 );
219
220 # fetch new filename list from database
221 $sth->{'filenames'}->execute() || die $sth->{'filenames'}->errstr();
222
223 # read them in with sesible defaults
224 while (my $row = $sth->{'filenames'}->fetchrow_hashref() ) {
225 $files{$row->{'filename'}} = {
226 size => $row->{'size'},
227 mode => $row->{'writable'} ? 0644 : 0444,
228 id => $row->{'id'} || 99,
229 };
230
231 my $d;
232 foreach (split(m!/!, $row->{'filename'})) {
233 # first, entry is assumed to be file
234 if ($d) {
235 $files{$d} = {
236 size => $dirs{$d}++,
237 mode => 0755,
238 type => 0040
239 };
240 $files{$d.'/.'} = {
241 mode => 0755,
242 type => 0040
243 };
244 $files{$d.'/..'} = {
245 mode => 0755,
246 type => 0040
247 };
248 }
249 $d .= "/" if ($d);
250 $d .= "$_";
251 }
252 }
253
254 print "found ",scalar(keys %files)-scalar(keys %dirs)," files, ",scalar(keys %dirs), " dirs\n";
255 }
256
257
258 sub filename_fixup {
259 my ($file) = shift;
260 $file =~ s,^/,,;
261 $file = '.' unless length($file);
262 return $file;
263 }
264
265 sub e_getattr {
266 my ($file) = filename_fixup(shift);
267 $file =~ s,^/,,;
268 $file = '.' unless length($file);
269 return -ENOENT() unless exists($files{$file});
270 my ($size) = $files{$file}{size} || 1;
271 my ($dev, $ino, $rdev, $blocks, $gid, $uid, $nlink, $blksize) = (0,0,0,1,0,0,1,1024);
272 my ($atime, $ctime, $mtime);
273 $atime = $ctime = $mtime = $files{$file}{ctime} || $ctime_start;
274
275 my ($modes) = (($files{$file}{type} || 0100)<<9) + $files{$file}{mode};
276
277 # 2 possible types of return values:
278 #return -ENOENT(); # or any other error you care to
279 #print(join(",",($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)),"\n");
280 return ($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks);
281 }
282
283 sub e_getdir {
284 my ($dirname) = shift;
285 $dirname =~ s!^/!!;
286 # return as many text filenames as you like, followed by the retval.
287 print((scalar keys %files)." files total\n");
288 my %out;
289 foreach my $f (sort keys %files) {
290 if ($dirname) {
291 if ($f =~ s/^\E$dirname\Q\///) {
292 $out{$f}++ if ($f =~ /^[^\/]+$/);
293 }
294 } else {
295 $out{$f}++ if ($f =~ /^[^\/]+$/);
296 }
297 }
298 if (! %out) {
299 $out{'no files? bug?'}++;
300 }
301 print scalar keys %out," files in dir '$dirname'\n";
302 print "## ",join(" ",keys %out),"\n";
303 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 {
319 # VFS sanity check; it keeps all the necessary state, not much to do here.
320 my $file = filename_fixup(shift);
321 my $flags = shift;
322
323 return -ENOENT() unless exists($files{$file});
324 return -EISDIR() unless exists($files{$file}{id});
325
326 read_content($file,$files{$file}{id}) unless exists($files{$file}{cont});
327
328 print "open '$file' ",length($files{$file}{cont})," bytes\n";
329 return 0;
330 }
331
332 sub e_read {
333 # return an error numeric, or binary/text string.
334 # (note: 0 means EOF, "0" will give a byte (ascii "0")
335 # to the reading program)
336 my ($file) = filename_fixup(shift);
337 my ($buf_len,$off) = @_;
338
339 return -ENOENT() unless exists($files{$file});
340
341 my $len = length($files{$file}{cont});
342
343 print "read '$file' [$len bytes] offset $off length $buf_len\n";
344
345 return -EINVAL() if ($off > $len);
346 return 0 if ($off == $len);
347
348 $buf_len = $len-$off if ($len - $off < $buf_len);
349
350 return substr($files{$file}{cont},$off,$buf_len);
351 }
352
353 sub clear_cont {
354 print "transaction rollback\n";
355 $dbh->rollback || die $dbh->errstr;
356 print "invalidate all cached content\n";
357 foreach my $f (keys %files) {
358 delete $files{$f}{cont};
359 }
360 print "begin new transaction\n";
361 #$dbh->begin_work || die $dbh->errstr;
362 }
363
364
365 sub update_db {
366 my $file = shift || die;
367
368 $files{$file}{ctime} = time();
369
370 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;
377 clear_cont;
378 return 0;
379 } else {
380 if (! $dbh->commit) {
381 print "ERROR: commit problem: ",$sth->{'update'}->errstr;
382 clear_cont;
383 return 0;
384 }
385 print "updated '$file' [",$files{$file}{id},"]\n";
386
387 $$fuse_self->{'invalidate'}->() if (ref $$fuse_self->{'invalidate'});
388 }
389 return 1;
390 }
391
392 sub e_write {
393 my $file = filename_fixup(shift);
394 my ($buffer,$off) = @_;
395
396 return -ENOENT() unless exists($files{$file});
397
398 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 $files{$file}{cont} = "";
404
405 $files{$file}{cont} .= substr($cont,0,$off) if ($off > 0);
406 $files{$file}{cont} .= $buffer;
407 $files{$file}{cont} .= substr($cont,$off+length($buffer),$len-$off-length($buffer)) if ($off+length($buffer) < $len);
408
409 $files{$file}{size} = length($files{$file}{cont});
410
411 if (! update_db($file)) {
412 return -ENOSYS();
413 } else {
414 return length($buffer);
415 }
416 }
417
418 sub e_truncate {
419 my $file = filename_fixup(shift);
420 my $size = shift;
421
422 print "truncate to $size\n";
423
424 $files{$file}{cont} = substr($files{$file}{cont},0,$size);
425 $files{$file}{size} = $size;
426 return 0
427 };
428
429
430 sub e_utime {
431 my ($atime,$mtime,$file) = @_;
432 $file = filename_fixup($file);
433
434 return -ENOENT() unless exists($files{$file});
435
436 print "utime '$file' $atime $mtime\n";
437
438 $files{$file}{time} = $mtime;
439 return 0;
440 }
441
442 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;
461 __END__
462
463 =head1 EXPORT
464
465 Nothing.
466
467 =head1 SEE ALSO
468
469 C<FUSE (Filesystem in USErspace)> website
470 L<http://sourceforge.net/projects/avf>
471
472 =head1 AUTHOR
473
474 Dobrica Pavlinusic, E<lt>dpavlin@rot13.orgE<gt>
475
476 =head1 COPYRIGHT AND LICENSE
477
478 Copyright (C) 2004 by Dobrica Pavlinusic
479
480 This library is free software; you can redistribute it and/or modify
481 it under the same terms as Perl itself, either Perl version 5.8.4 or,
482 at your option, any later version of Perl 5 you may have available.
483
484
485 =cut
486

Properties

Name Value
svn:executable

  ViewVC Help
Powered by ViewVC 1.1.26