updated on Thu Jan 19 20:01:47 UTC 2012
[aur-mirror.git] / perl-ramfuse / InMemory.pm
blob7f2ae648a1fa56ed090e470d66983b7150ce38c5
1 package Fuse::InMemory;
2 use Getopt::Long;
3 use Fuse ':xattr';
4 use Errno ':POSIX';
5 use Fcntl ':mode';
7 # create a new instance
8 sub new {
9 my $class = shift;
10 my $self = bless {
11 'root' => Fuse::InMemory::File->new(
12 'path' => '/',
13 'mode' => S_IFDIR|0755,
15 }, $class;
16 return $self;
19 # enter the main loop
20 sub main {
21 my ($self, @args) = @_;
22 local @ARGV = @args if @args;
23 # default arguments
24 my %fuse_args = (
25 'debug' => 0,
26 'mountopts' => 'default_permissions,allow_other',
28 # specified arguments supercede the defaults
29 GetOptions(
30 'debug' => \$fuse_args{'debug'},
31 'o=s' => \$fuse_args{'mountopts'},
32 ) or Getopt::Long::HelpMessage(2);
33 $fuse_args{'mountpoint'} = shift @ARGV unless defined $fuse_args{'mountpoint'};
34 # install Fuse callbacks
35 for my $f (keys %{*{'Fuse::InMemory::'}}) {
36 if($f =~ /^fuse_([a-z]+)$/) {
37 $fuse_args{$1} = eval 'sub { my $before = $self->check_before("'.$1.'", @_); return $before if defined $before; return $self->check_after("'.$1.'",$self->fuse_'.$1.'(@_)); }';
40 # run Fuse main loop
41 Fuse::main(%fuse_args);
44 # run the before filter if there is one
45 sub check_before() {
46 my ($self, $cb, @args) = @_;
47 my $check = $self->{'before_'.$cb};
48 return $check->($self, $cb, @args) if ref $check;
49 return undef;
52 # run the after filter if there is one
53 sub check_after() {
54 my ($self, $cb, @args) = @_;
55 my $check = $self->{'after_'.$cb};
56 return $check->($self, $cb, @args) if ref $check;
57 return $args[0] unless wantarray;
58 return @args;
61 # locate a file object by path name, optionally create it if a replacement is specified
62 sub find_file {
63 my ($self, $filename, $replacement) = @_;
64 my $res = $self->{'root'};
65 my $last = undef;
67 my @parts = split /\//, $filename;
68 while(@parts) {
69 my $dir = shift @parts;
70 next if $dir eq '';
71 return -ENOENT() unless ref $res;
72 return -ENOTDIR() unless $res->is_directory();
73 $last = $res;
74 $res = $res->get_file($dir);
76 if(defined $replacement && !ref $res && defined $last) {
77 $res = $replacement;
78 $last->add_file($res);
80 $res = Fuse::InMemory::File->new(path => $filename, exists => 0) unless ref $res;
81 return $res;
84 # add magic to a file by path name
85 sub add_magic {
86 my($self, $filename, $reader, $writer, $truncater, $flusher) = @_;
87 my $file = $self->find_file($filename);
88 return $file unless ref $file;
89 $file->{'reader'} = $reader;
90 $file->{'writer'} = $writer;
91 $file->{'flusher'} = $flusher;
92 $file->{'truncater'} = $truncater;
93 return 0;
96 # getattr callback
97 sub fuse_getattr {
98 my ($self, $filename) = @_;
99 my $file = $self->find_file($filename);
100 return $file unless ref $file;
101 return -ENOENT() unless $file->exists();
102 # print "getattr $filename\n";
103 # $file->{'open'}++;
104 # $file->flush();
105 # $file->{'open'}--;
106 my @ret = $file->getattr();
107 return @ret;
110 # readlink callback
111 sub fuse_readlink {
112 my ($self, $filename) = @_;
113 my $file = $self->find_file($filename);
114 return $file unless ref $file;
115 my $ret = $file->is_symlink();
116 $ret = -EINVAL() unless $ret;
117 return $ret;
120 # getdir callback
121 sub fuse_getdir {
122 my ($self, $filename) = @_;
123 my $file = $self->find_file($filename);
124 return $file unless ref $file;
125 return -ENOTDIR() unless $file->is_directory();
126 return $file->getdir();
129 # mknod callback
130 sub fuse_mknod {
131 my ($self, $filename, $mode, $device) = @_;
132 my $file = $self->find_file($filename);
133 return $file unless ref $file;
134 return -EEXIST() if $file->exists();
135 $file->create($self);
136 return 0;
139 # mkdir callback
140 sub fuse_mkdir {
141 my ($self, $filename, $mode) = @_;
142 my $file = $self->find_file($filename);
143 return $file unless ref $file;
144 return -EEXIST() if $file->exists();
145 $file->set_mode(S_IFDIR | $mode);
146 $file->create($self);
147 return 0;
150 # unlink callback
151 sub fuse_unlink {
152 my ($self, $filename) = @_;
153 my $file = $self->find_file($filename);
154 return $file unless ref $file;
155 return -ENOENT() unless $file->exists();
156 $file->flush();
157 $file->remove($self);
158 return 0;
161 # rmdir callback
162 sub fuse_rmdir {
163 my ($self, $filename) = @_;
164 my $file = $self->find_file($filename);
165 return $file unless ref $file;
166 return -ENOENT() unless $file->exists();
167 return -ENOTDIR() unless $file->is_directory();
168 return -ENOTEMPTY() unless $file->is_empty();
169 $file->remove($self);
170 return 0;
173 # symlink callback
174 sub fuse_symlink {
175 my ($self, $filename, $linkname) = @_;
176 my $file = $self->find_file($linkname);
177 return $file unless ref $file;
178 return -EEXIST() if $file->exists();
179 $file->make_symlink($filename);
180 $file->create($self);
181 return 0;
184 # rename callback
185 sub fuse_rename {
186 my ($self, $filename, $newname) = @_;
187 my $file = $self->find_file($filename);
188 my $newn = $self->find_file($newname);
189 return $file unless ref $file;
190 return $newn unless ref $newn;
191 return -EEXIST() if $newn->exists();
192 $file->moved($newname);
193 $self->find_file($newname, $file);
194 return 0;
197 # link callback
198 sub fuse_link {
199 my ($self, $filename, $linkname) = @_;
200 my $file = $self->find_file($linkname);
201 my $dest = $self->find_file($filename);
202 return $file unless ref $file;
203 return $dest unless ref $dest;
204 return -EEXIST() if $file->exists();
205 return -EPERM() unless $dest->exists();
206 $file->create($self);
207 return $file->make_hardlink($dest);
210 # chmod callback
211 sub fuse_chmod {
212 my ($self, $filename, $mode) = @_;
213 my $file = $self->find_file($filename);
214 return $file unless ref $file;
215 return $file->chmod($mode);
218 # chown callback
219 sub fuse_chown {
220 my ($self, $filename, $uid, $gid) = @_;
221 my $file = $self->find_file($filename);
222 return $file unless ref $file;
223 return $file->chown($uid, $gid);
226 # truncate callback
227 sub fuse_truncate {
228 my ($self, $filename, $offset) = @_;
229 my $file = $self->find_file($filename);
230 return $file unless ref $file;
231 return -ENOENT() unless $file->exists();
232 return $file->truncate();
235 # utime callback
236 sub fuse_utime {
237 my ($self, $filename, $atime, $mtime) = @_;
238 my $file = $self->find_file($filename);
239 return $file unless ref $file;
240 return -ENOENT() unless $file->exists();
241 $file->set_amtime($atime, $mtime);
242 return 0;
245 # open callback
246 sub fuse_open {
247 my ($self, $filename, $flags) = @_;
248 my $file = $self->find_file($filename);
249 return $file unless ref $file;
250 return -ENOENT() unless $file->exists();
251 $file->{'open'} = 1;
252 $file->flush();
253 return 0;
256 # read callback
257 sub fuse_read {
258 my ($self, $filename, $size, $offset) = @_;
259 my $file = $self->find_file($filename);
260 return $file unless ref $file;
261 return $file->read($size, $offset);
264 # write callback
265 sub fuse_write {
266 my ($self, $filename, $buffer, $offset) = @_;
267 my $file = $self->find_file($filename);
268 return $file unless ref $file;
269 return $file->write($buffer, $offset);
272 # statfs callback
273 sub fuse_statfs {
274 my ($self) = @_;
275 return (0, 1, 1, 1, 1, 1);
276 # return $namelen, $files, $files_free, $blocks, $blocks_avail, $blocksize
277 # return -ENOSYS();
280 # flush callback
281 sub fuse_flush {
282 my ($self, $filename) = @_;
283 my $file = $self->find_file($filename);
284 $file->flush() if ref $file;
285 return 0;
288 # release callback
289 sub fuse_release {
290 my ($self, $filename, $flags) = @_;
291 my $file = $self->find_file($filename);
292 $file->{'open'} = 0;
293 $file->flush() if ref $file;
294 return 0;
297 # fsync callback
298 sub fuse_fsync {
299 my ($self, $filename, $flags) = @_;
300 my $file = $self->find_file($filename);
301 $file->flush() if ref $file;
302 return 0;
305 # setxattr callback
306 sub fuse_setxattr {
307 my ($self, $filename, $ext_name, $ext_val, $flags) = @_;
308 my $file = $self->find_file($filename);
309 return $file unless ref $file;
310 return -EEXIST() if ($flags & XATTR_CREATE()) && exists $file->{'xattr'}->{$ext_name};
311 # return -ENOATTR() if ($flags & XATTR_REPLACE()) && exists $file->{'xattr'}->{$ext_name};
312 $ext_val = 0 unless defined $ext_val;
313 $file->{'xattr'}->{$ext_name} = $ext_val;
314 return 0;
317 # getxattr callback
318 sub fuse_getxattr {
319 my ($self, $filename, $ext_name) = @_;
320 my $file = $self->find_file($filename);
321 return $file unless ref $file;
322 # return -ENOATTR() unless exists $file->{'xattr'}->{$ext_name};
323 return $file->{'xattr'}->{$ext_name};
326 # listxattr callback
327 sub fuse_listxattr {
328 my ($self, $filename) = @_;
329 my $file = $self->find_file($filename);
330 return $file unless ref $file;
331 return (keys %{$file->{'xattr'}}), 0;
334 # removexattr callback
335 sub fuse_removexattr {
336 my ($self, $filename, $ext_name) = @_;
337 delete $file->{'xattr'}->{$ext_name};
338 return 0;
341 # class representing a single file or directory
342 package Fuse::InMemory::File;
343 use Errno qw / :POSIX /;
344 use Fcntl ':mode';
346 # create a new instance with sensible defaults
347 sub new {
348 my $class = shift;
349 my $time = time();
350 my $tmp = '';
351 my $self = {
352 'exists' => 1, # zero if only a temporary file object
353 'data' => \$tmp, # reference to empty scalar var for contents of the file
354 'attr' => [ # file attributes
355 0, # device number of file system
356 0, # inode number
357 S_IFREG|0644, # file mode
358 1, # number of hard links to this file
359 $>, # user ID of owner
360 $)+0, # group ID of owner
361 0, # device identifier for special nodes
362 0, # size in bytes
363 $time, # atime
364 $time, # mtime
365 $time, # ctime
366 512, # blksize
367 1, # blocks
369 'xattr' => { # extended attributes
372 # copy arguments
373 while(my $a = shift) {
374 last if !defined $a;
375 my $b = shift;
376 if($a eq 'mode') {
377 $self->{'attr'}->[2] = $b;
378 } else {
379 $self->{$a} = $b;
382 return bless $self, $class;
385 # read $size bytes from $offset
386 sub read {
387 my ($self, $size, $offset) = @_;
388 return '' if $offset >= $self->{'attr'}->[7];
389 my $diff = $offset + $size - $self->{'attr'}->[7];
390 $size -= $diff if $diff > 0;
391 if($self->{'reader'}) {
392 # apply magic
393 return $self->{'reader'}->($self, $size, $offset);
394 } else {
395 return substr(${$self->{'data'}}, $offset, $size);
399 # write $buffer to $offset in this file
400 sub write {
401 my ($self, $buffer, $offset) = @_;
402 if($self->{'writer'}) {
403 # apply magic
404 return $self->{'writer'}->($self, $buffer, $offset);
405 } else {
406 my $extend = $offset - length(${$self->{'data'}});
407 ${$self->{'data'}} .= (' ' x $extend) if $extend > 0;
408 substr(${$self->{'data'}}, $offset, length($buffer)) = $buffer;
409 $self->set_size(length(${$self->{'data'}}));
410 return length($buffer);
414 # truncate this file
415 sub truncate {
416 my ($self, $buffer, $offset) = @_;
417 if($self->{'truncater'}) {
418 # apply magic
419 return $self->{'truncater'}->($self);
420 } else {
421 if($self->{'attr'}->[3] <= 1) {
422 # if only one link, create new scalar for contents, saves memory
423 my $tmp = '';
424 $self->{'data'} = \$tmp;
425 } else {
426 substr(${$self->{'data'}},0) = '';
428 $self->set_size(0);
429 return 0;
433 # flush
434 sub flush {
435 my ($self) = @_;
436 if($self->{'flusher'}) {
437 # apply magic
438 $self->{'flusher'}->($self);
442 # return the path of this file
443 sub path {
444 my ($self) = @_;
445 return $self->{'path'};
448 # return the name of this file only
449 sub filename {
450 my ($self) = @_;
451 my $p = $self->{'path'};
452 $p =~ s/.*\///;
453 return $p;
456 # return true if this file actually exists
457 sub exists {
458 my ($self) = @_;
459 return $self->{'exists'};
462 # set the size of this file to $size
463 sub set_size {
464 my ($self, $size) = @_;
465 $self->{'attr'}->[7] = $size;
466 $self->flush();
467 return $size;
470 # set the mode of this file to $mode
471 sub set_mode {
472 my ($self, $mode) = @_;
473 $self->{'attr'}->[2] = $mode;
474 $self->flush();
475 return $mode;
478 # set the permissions of this file to $mode
479 sub chmod {
480 my ($self, $mode) = @_;
481 $self->{'attr'}->[2] = ($self->{'attr'}->[2] & S_IFMT()) | $mode;
482 $self->flush();
483 return 0;
486 # set owner of this file to $uid and group to $gid
487 sub chown {
488 my ($self, $uid, $gid) = @_;
489 $self->{'attr'}->[4] = $uid;
490 $self->{'attr'}->[5] = $gid;
491 $self->flush();
492 return 0;
495 # set atime to $atime and mtime to $mtime
496 sub set_amtime {
497 my ($self, $atime, $mtime) = @_;
498 $self->{'attr'}->[8] = $atime;
499 $self->{'attr'}->[9] = $mtime;
500 $self->flush();
503 # make this file into a symlink pointing to $dest
504 sub make_symlink {
505 my ($self, $dest) = @_;
506 $self->{'attr'}->[2] = S_IFLNK|0777;
507 $self->{'symlink'} = $dest;
508 $self->flush();
511 # make this file into a hard link pointing to $dest
512 # destination must be within the same file system
513 sub make_hardlink {
514 my ($self, $dest) = @_;
515 $self->{'attr'} = $dest->{'attr'};
516 $self->{'data'} = $dest->{'data'};
517 $self->{'xattr'} = $dest->{'xattr'};
518 $self->{'attr'}->[3]++;
519 $self->flush();
522 # return true if this file is actually a symlink
523 sub is_symlink {
524 my ($self) = @_;
525 return $self->{'symlink'} if ($self->{'attr'}->[2] & S_IFLNK) != 0;
526 return 0;
529 # return true if this file is a directory
530 sub is_directory {
531 my ($self) = @_;
532 return ($self->{'attr'}->[2] & S_IFDIR) != 0;
535 # return true if this is a directory and also empty
536 sub is_empty {
537 my ($self) = @_;
538 return -ENOTDIR() unless $self->is_directory();
539 $self->flush();
540 $self->{'contents'} = {} unless exists $self->{'contents'};
541 return 0 if scalar(keys %{$self->{'contents'}}) > 0;
542 return 1;
545 # remove the child $child from this directory
546 sub remove_child {
547 my ($self, $child) = @_;
548 return -ENOTDIR() unless $self->is_directory();
549 $self->{'contents'} = {} unless exists $self->{'contents'};
550 delete $self->{'contents'}->{$child->filename()};
551 $self->flush();
552 return 0;
555 # get the file identified by name $file within this directory
556 sub get_file {
557 my ($self, $file) = @_;
558 return undef unless $self->is_directory();
559 $self->flush();
560 $self->{'contents'} = {} unless exists $self->{'contents'};
561 my $ret = $self->{'contents'}->{$file};
562 return -ENOENT() unless defined $ret;
563 return $ret;
566 # add the file $file to this directory
567 sub add_file {
568 my ($self, $file) = @_;
569 $self->{'contents'}->{$file->filename()} = $file;
570 $file->{'parent'} = $self;
571 $self->flush();
574 # return the list of this directory's contents plus . and ..
575 sub getdir {
576 my ($self) = @_;
577 return undef unless $self->is_directory();
578 $self->flush();
579 return '.', '..', keys %{$self->{'contents'}}, 0;
582 # create this file and put it into the file system $fs
583 sub create {
584 my ($self, $fs) = @_;
585 $fs->find_file($self->{'path'}, $self);
586 $self->{'exists'} = 1;
589 # remove this file
590 sub remove {
591 my ($self, $fs) = @_;
592 $self->{'exists'} = 0;
593 $self->{'attr'}->[3]--;
594 $self->{'parent'}->remove_child($self);
595 $self->truncate() if $self->{'attr'}->[3] < 1;
596 $self->flush();
599 # do stuff necessary after this file was moved about in the file system, $name contains the new path
600 sub moved {
601 my ($self, $name) = @_;
602 $self->{'parent'}->remove_child($self);
603 $self->{'path'} = $name;
604 $self->flush();
607 # return the attributes of this file
608 sub getattr {
609 my ($self) = @_;
610 $self->flush();
611 my @ret = @{$self->{'attr'}};
612 if($self->is_directory()) {
613 # nlink is 1 + number of subdirectories for directories
614 $ret[3] += scalar(keys %{$self->{'contents'}});
616 return @ret;
620 __END__
622 =head1 NAME
624 Fuse::InMemory - extensible RAM file system
626 =head1 SYNOPSIS
628 use Fuse::InMemory;
629 my $fs = Fuse::InMemory->new();
630 $fs->main();
632 =head1 DESCRIPTION
634 This is a complete RAM file system written in perl, using Fuse.
635 By default, all file content is kept in scalar buffers. Attributes
636 and extended attributes are stored separately, and mostly
637 supported. This means you can use chmod, chown, setfacl and all
638 that on files in this file system, although the usual restrictions
639 on permissions apply - i.e. for example, if you disable permission
640 checking, you can create a setuid-root executable as a normal user,
641 but when you run it, it won't run with root privileges.
643 =head2 EXPORTED SYMBOLS
645 None.
647 =head2 CLASS METHODS
649 =head3 new()
651 Creates a new RAM file system.
653 my $fs = Fuse::InMemory->new();
655 =head2 INSTANCE METHODS
657 =head3 main([arguments])
659 Configures the file system and enters the Fuse main loop.
660 When run without arguments, options will be taken from the
661 command line. This includes the mount point.
663 $fs->main();
665 It is also possible to pass a list of arguments to main, to be
666 used instead of the command line:
668 $fs->main(@my_args);
672 $fs->main('/mnt', '-o', '');
674 =head3 add_magic(file,reader,writer,truncater,flusher)
676 Add magic to a file. The file must already exist and is
677 identified by its full path within the mounted file system.
678 All remaining arguments are coderefs.
680 =head1 AUTHOR
682 Daniel Fischer, E<lt>df@erinye.comE<gt>
684 =head1 SEE ALSO
686 L<Fuse>
688 =cut