File Coverage

blib/lib/Fuse/Simple.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             package Fuse::Simple; # in file Fuse/Simple.pm
3              
4             =head1 NAME
5              
6             Fuse::Simple - Simple way to write filesystems in Perl using FUSE
7              
8             =head1 SYNOPSIS
9              
10             use Fuse::Simple qw(accessor main);
11             my $var = "this is a variable you can modify. write to me!\n";
12             my $filesystem = {
13             foo => "this is the contents of a file called foo\n",
14             subdir => {
15             "foo" => "this foo is in a subdir called subdir\n",
16             "blah" => "this blah is in a subdir called subdir\n",
17             },
18             "blah" => \ "subdir/blah", # scalar refs are symlinks
19             "magic" => sub { return "42\n" }, # will be called to get value
20             "var" => accessor(\$var), # read and write this variable
21             "var2" => accessor(\$var), # and the same variable
22             "var.b" => accessor(\ my $tmp), # and an anonymous var
23             };
24             main(
25             "mountpoint" => "/mnt", # actually optional
26             "debug" => 0, # for debugging Fuse::Simple. optional
27             "fuse_debug" => 0, # for debugging FUSE itself. optional
28             "threaded" => 0, # optional
29             "/" => $filesystem, # required :-)
30             );
31              
32             =head1 DESCRIPTION
33              
34             B lets you write filesystems in Perl. B makes this
35             REALLY Simple, as you just need a hash for your root directory,
36             containing strings for files, more hashes for subdirs, or functions
37             to be called for magical functionality a bit like F.
38              
39             =cut
40              
41             ######################################################################
42             # By "Nosey" Nick Waterman of Nilex
43             # http://noseynick.org/
44             # (C) Copyright 2006 Nilex - All wrongs righted, all rights reserved.
45             ######################################################################
46             # Requirements:
47 2     2   50600 use 5.008;
  2         9  
  2         99  
48 2     2   14 use strict;
  2         4  
  2         94  
49 2     2   12 use warnings;
  2         9  
  2         278  
50 2     2   13 use Carp;
  2         4  
  2         210  
51 2     2   977 use Fuse;
  0            
  0            
52             use Errno qw(:POSIX); # ENOENT EISDIR etc
53             use Fcntl qw(:DEFAULT :mode); # S_IFREG S_IFDIR, O_SYNC O_LARGEFILE etc.
54             use Switch;
55             # use diagnostics;
56              
57             ######################################################################
58             # Module stuff:
59             ######################################################################
60             use Exporter;
61             our @ISA = qw(Exporter);
62             our $VERSION = '1.00';
63              
64             # thou shalt not pollute, thou shalt not export more than thou needest.
65             our @EXPORT = qw( );
66             our @EXPORT_OK = qw(
67             main fetch runcode saferun fserr nocache wrap quoted
68             dump_open_flags accessor easy_getattr
69             fs_not_imp fs_flush fs_getattr fs_getdir fs_open fs_read fs_readlink
70             fs_release fs_statfs fs_truncate fs_write
71             );
72             our %EXPORT_TAGS =
73             (
74             'all' => \@EXPORT_OK,
75             'DEFAULT' => \@EXPORT,
76             'usual' => [qw(main accessor fserr nocache)],
77             'debug' => [qw(wrap quoted dump_open_flags)],
78             'tools' => [qw(fetch runcode saferun easy_getattr)],
79             'filesys' => [qw(
80             fs_not_imp fs_flush fs_getattr fs_getdir fs_open fs_read
81             fs_readlink fs_release fs_statfs fs_truncate fs_write
82             )],
83             );
84              
85             =head1 IMPORT TAGS
86              
87             B exports nothing by default, but individual functions
88             can be exported, or any ofthe following tags:
89              
90             =over
91              
92             =item :usual
93              
94             Includes: main accessor fserr nocache
95              
96             =item :debug
97              
98             Includes: wrap quoted dump_open_flags
99              
100             =item :tools
101              
102             Includes: fetch runcode saferun easy_getattr
103              
104             =item :filesys
105              
106             Includes:
107             fs_not_imp fs_flush fs_getattr fs_getdir fs_open fs_read
108             fs_readlink fs_release fs_statfs fs_truncate fs_write
109              
110             =back
111              
112             =begin testing
113              
114             BEGIN { use_ok( 'Fuse::Simple', qw(:usual :debug :tools :filesys)); }
115              
116             =end testing
117              
118             =cut
119              
120             ######################################################################
121             # Some useful stuff
122             ######################################################################
123              
124             our $debug = 0; # can be set if you really really need it to be
125             my $ctime = time();
126             my $uid = $>;
127             my $gid = $) + 0;
128             our $fs = {
129             # "empty" dir by default
130             "README" => "You forgot to pass a '/' parameter to Fuse::Simple::main!\n"
131             };
132              
133             ######################################################################
134              
135             =head1 MAIN FUNCTION
136              
137             =over
138              
139             =item B
(B => I, ...)
140              
141             Mount your filesystem, and probably never return. Arguments are:
142              
143             =over
144              
145             =item B => I<"/mnt">,
146              
147             This is actually optional. If you don't supply a mountpoint, it'll
148             take it from @ARGV !
149              
150             =item B => I<0|1>,
151              
152             Debug Fuse::Simple. All filesystem calls, arguments, and return values
153             will be dumped, a bit like L for perl.
154              
155             =item B => I<0|1>,
156              
157             Debug FUSE itself. More low-level than B
158              
159             =item B => I<0|1>,
160              
161             See L
162              
163             =item B<"/"> => { hash for your root directory },
164              
165             =item B B B B B B etc
166              
167             See L
168              
169             You can replace any of the low-level functions if you want, but if
170             you wanted to mess around with the dirty bits, you'd probably not be
171             using L, would you?
172              
173             =item others
174              
175             If I've forgotten any L args, you can supply them too.
176              
177             =back
178              
179             =back
180              
181             =cut
182              
183             sub main {
184             # some default args
185             my %args = (
186             "mountpoint" => $ARGV[0] || "",
187             "debug" => $debug,
188             "fuse_debug" => 0,
189             "threaded" => 0,
190             "/" => $fs,
191             );
192             # the default subs
193             my %fs_subs = (
194             "chmod" => \&fs_not_imp,
195             "chown" => \&fs_not_imp,
196             "flush" => \&fs_flush,
197             "fsync" => \&fs_not_imp,
198             "getattr" => \&fs_getattr,
199             "getdir" => \&fs_getdir,
200             "getxattr" => \&fs_not_imp,
201             "link" => \&fs_not_imp,
202             "listxattr" => \&fs_not_imp,
203             "mkdir" => \&fs_not_imp,
204             "mknod" => \&fs_not_imp,
205             "open" => \&fs_open,
206             "read" => \&fs_read,
207             "readlink" => \&fs_readlink,
208             "release" => \&fs_release,
209             "removexattr" => \&fs_not_imp,
210             "rmdir" => \&fs_not_imp,
211             "rename" => \&fs_not_imp,
212             "setxattr" => \&fs_not_imp,
213             "statfs" => \&fs_statfs,
214             "symlink" => \&fs_not_imp,
215             "truncate" => \&fs_truncate,
216             "unlink" => \&fs_not_imp,
217             "utime" => sub{return 0},
218             "write" => \&fs_write,
219             );
220             my $name;
221             # copy across the arg supplied to main()
222             while ($name = shift) {
223             $args{$name} = shift;
224             }
225             # except extract these ones back out.
226             $debug = delete $args{"debug"};
227             $args{"debug"} = delete( $args{"fuse_debug"} ) || 0;
228             $fs = delete $args{"/"};
229             # add the functions, if not already defined.
230             # wrap in debugger if debug is set.
231             for $name (keys %fs_subs) {
232             my $sub = $fs_subs{$name};
233             $sub = wrap($sub, $name) if $debug;
234             $args{$name} ||= $sub;
235             }
236             Fuse::main(%args);
237             }
238              
239             =head1 UTIL FUNCTIONS
240              
241             These might be useful for people writing their own filesystems
242              
243             =over
244              
245             =item B(I<$path, @args>) (not exported)
246              
247             Given F, return the F dir or file or
248             whatever. @args will be passed to the final coderef if supplied.
249              
250             =begin testing
251              
252             is(fetch("README"), $Fuse::Simple::fs->{README}, "fetch() test");
253              
254             =end testing
255              
256             =cut
257              
258             sub fetch {
259             my ($path, @args) = @_;
260            
261             my $obj = $fs;
262             for my $elem (split '/', $path) {
263             next if $elem eq ""; # skip empty // and before first /
264             $obj = runcode($obj); # if there's anything to run
265             # the dir we're changing into must be a hash (dir)
266             return fserr(ENOTDIR()) unless ref($obj) eq "HASH";
267             # note that ENOENT and undef are NOT the same thing!
268             return fserr(ENOENT()) unless exists $obj->{$elem};
269             $obj = $obj->{$elem};
270             }
271            
272             return runcode($obj, @args);
273             }
274              
275             =item B(I<$code, @args>) (not exported)
276              
277             B, run it, or return our cached version
278             return after all CODE refs have been followed.
279             also returns first arg if it wasn't a coderef.
280              
281             =begin testing
282              
283             is(runcode("foo"), "foo", "runcode with string");
284             is_deeply(runcode(["A","B","C"]), ["A","B","C"], "runcode with arrayref");
285             is_deeply(runcode({"A"=>"B"}), {"A"=>"B"}, "runcode with hashref");
286             is(runcode(undef), undef, "runcode with undef");
287             is(runcode(sub {return "foo"}), "foo", "runcode with foo");
288             is(runcode(sub {return shift}, "foo"), "foo", "runcode with an arg");
289             is_deeply(runcode(sub{return{"a"=>"b"}}, {"a"=>"b"}), {"a"=>"b"},
290             "runcode sub returns hash");
291              
292             =end testing
293              
294             =cut
295              
296             my %codecache = ();
297             sub runcode {
298             my ($obj, @args) = @_;
299            
300             while (ref($obj) eq "CODE") {
301             my $old = $obj;
302             if (@args) { # run with these args. don't cache
303             delete $codecache{$old};
304             print "running $obj(",quoted(@args),") NO CACHE\n" if $debug;
305             $obj = saferun($obj, @args);
306             } elsif (exists $codecache{$obj}) { # found in cache
307             print "got cached $obj\n" if $debug;
308             $obj = $codecache{$obj}; # could be undef, or an error, BTW
309             } else {
310             print "running $obj() to cache\n" if $debug;
311             $obj = $codecache{$old} = saferun($obj);
312             }
313            
314             if (ref($obj) eq "NOCACHE") {
315             print "returned a nocache() value - flushing\n" if $debug;
316             delete $codecache{$old};
317             $obj = $$obj;
318             }
319            
320             print "returning ",ref($obj)," ",
321             defined($obj) ? $obj : "undef",
322             "\n" if $debug;
323             }
324             return $obj;
325             }
326              
327             =item B(I<$sub>,I<@args>)
328              
329             Runs the supplied $sub coderef, safely (IE catches die() etc),
330             returns something usable by the rest of Fuse::Simple.
331              
332             =begin testing
333              
334             is(saferun(sub{"foo"}), "foo", "saferun string");
335             is(saferun(sub{shift}, "foo"), "foo", "saferun arg");
336             is(ref(saferun(sub{die "foo"})), "ERROR", "saferun error");
337             is_deeply(saferun(sub{die ["foo"]}), ["foo"], "saferun array die");
338              
339             =end testing
340              
341             =cut
342              
343             sub saferun {
344             my ($sub, @args) = @_;
345            
346             my $ret = eval { &$sub(@args) };
347             my $died = $@;
348             if (ref($died)) {
349             # we can die fserr(ENOTSUP) if we want!
350             print "+++ Error $$died\n" if ref($died) eq "ERROR";
351             return $died;
352             } elsif ($died) {
353             print "+++ $died\n";
354             # stale file handle? moreorless?
355             return fserr(ESTALE());
356             }
357             return $ret;
358             }
359              
360             =item B(I<$error_number>)
361              
362             Used by called coderef files, to return an error indication, for example:
363              
364             return fserr(E2BIG());
365              
366             =begin testing
367              
368             is(ref(fserr("foo")), "ERROR", "fserr ref type");
369             is(${&fserr("foo")}, "foo", "fserr arg passed");
370              
371             =end testing
372              
373             =cut
374              
375             sub fserr {
376             return bless(\ shift, "ERROR"); # yup, utter abuse of bless :-)
377             }
378              
379             =item B(I<$stuff_to_return>)
380              
381             Used by called coderef files, to return something that should not be cached.
382              
383             =begin testing
384              
385             is(ref(nocache("foo")), "NOCACHE", "nocache ref type");
386             is(${&nocache("foo")}, "foo", "nocache arg passed");
387              
388             =end testing
389              
390             =cut
391              
392             sub nocache {
393             return bless(\ shift, "NOCACHE"); # yup, utter abuse of bless :-)
394             }
395              
396             =item B(I<$sub, @name_etc>)
397              
398             Wrap a function with something that'll dump args on the way in
399             and return values on the way out.
400             This is a debugging fuction, sorta like L for perl really.
401              
402             =begin testing
403              
404             my $test = wrap(sub {return "foo".(shift||"")}, "foo");
405             is(ref($test), "CODE", "wrap a coderef");
406             is(&$test(), "foo", "wrapped coderef returns expected");
407             is(&$test("bar"), "foobar", "wrapped coderef args work");
408              
409             =end testing
410              
411             =cut
412              
413             my @indent = ();
414             sub wrap {
415             my ($sub, @name_etc) = @_;
416            
417             return sub {
418             print "@indent> @name_etc(", quoted(@_), ")\n";
419             push @indent, " ";
420             my @ret = eval { &$sub(@_) };
421             my $died = $@;
422             pop @indent;
423             die $died if ref($died); # die(some object), EG die(fserr(E2BIG))
424             die "@indent! $died" if $died;
425             print "@indent< =", quoted(@ret), "\n";
426             return wantarray ? @ret : $ret[0];
427             };
428             }
429              
430             =item B(I<@list>)
431              
432             return a nice printable version of the args, a little like
433             Data::Dumper would
434              
435             =begin testing
436              
437             is(quoted("foo"), '"foo"', "quoting");
438             is(quoted('\\'), '"\\\\"', "quoting backslash");
439             is(quoted("\$\@\"\t\r\n\f\a\e"), '"\$\@\"\t\r\n\f\a\e"', "quoting fun");
440             is(quoted('42'), '42', "unquoted numbers");
441             is(quoted(1,2,3), '1, 2, 3', "quoted list");
442              
443             =end testing
444              
445             =cut
446              
447             my %escaped = (
448             '$' => '$', '@' => '@', '"' => '"', "\\" => "\\",
449             "\t" => "t", "\r" => "r", "\n" => "n",
450             "\f" => "f", "\a" => "a", "\e" => "e",
451             );
452             sub quoted {
453             my @ret = ();
454            
455             for my $n (@_) {
456             # special case for undefined vars:
457             if (not defined($n)) { push @ret, "undef"; next; }
458             # digits (that are really digits without newlines) can be printed
459             # without quoting:
460             if ($n =~ /^-?\d+\.?\d*$/ && $n !~ /\n/) { push @ret, $n; next; }
461            
462             # other stuff needs quoting and escaping in fun ways:
463             my $s = $n;
464             $s =~ s/([\$\@\"\\\t\n\r\f\a\e])/\\$escaped{$1}/g;
465             $s =~ s/([^ -~])/sprintf('\x{%x}',ord($1))/ge;
466             push @ret, '"'.$s.'"';
467             }
468             return join(", ", @ret);
469             }
470              
471             =item B(I<$flags>)
472              
473             Translate the flags to the open() call
474              
475             =cut
476              
477             sub dump_open_flags {
478             my $flags = shift;
479            
480             printf " flags: 0%o = (", $flags;
481             for my $bits (
482             [ O_ACCMODE(), O_RDONLY(), "O_RDONLY" ],
483             [ O_ACCMODE(), O_WRONLY(), "O_WRONLY" ],
484             [ O_ACCMODE(), O_RDWR(), "O_RDWR" ],
485             [ O_APPEND(), O_APPEND(), "|O_APPEND" ],
486             [ O_NONBLOCK(), O_NONBLOCK(), "|O_NONBLOCK" ],
487             [ O_SYNC(), O_SYNC(), "|O_SYNC" ],
488             [ O_DIRECT(), O_DIRECT(), "|O_DIRECT" ],
489             [ O_LARGEFILE(), O_LARGEFILE(), "|O_LARGEFILE" ],
490             [ O_NOFOLLOW(), O_NOFOLLOW(), "|O_NOFOLLOW" ],
491             ) {
492             my ($mask, $flag, $name) = @$bits;
493             if (($flags & $mask) == $flag) {
494             $flags -= $flag;
495             print $name;
496             }
497             }
498             printf "| 0%o !!!", $flags if $flags;
499             print ")\n";
500             }
501              
502             =item B(I<\$var>)
503              
504             return a sub that can be used to read and write the (scalar) variable $var:
505              
506             my $var = "default value";
507             my $fs = { "filename" => accessor(\$var) };
508              
509             This accessor is a bit over-simple, doesn't handle multi-block writes,
510             partial block writes, seeked reads, non-saclar values,
511             or anything particularly clever.
512              
513             =begin testing
514              
515             my $foo = undef;
516             my $acc = accessor(\$foo);
517              
518             is(ref($acc), "CODE", "accessor is a coderef");
519             is($foo, undef, "undef at first");
520             is(&$acc(), undef, "undef thru accessor");
521              
522             &$acc("foo");
523             is($foo, "foo", "foo was set");
524             is(&$acc(), "foo", "foo thru accessor");
525              
526             $foo="bar";
527             is(&$acc(), "bar", "bar thru accessor");
528              
529             =end testing
530              
531             =cut
532              
533             sub accessor {
534             my $var_ref = shift;
535            
536             croak "accessor() requires a reference to a scalar var\n"
537             unless defined($var_ref) && ref($var_ref) eq "SCALAR";
538            
539             return sub {
540             my $new = shift;
541             $$var_ref = $new if defined($new);
542             return $$var_ref;
543             }
544             }
545              
546             =item B(I<$mode, $size>)
547              
548             Internal function, to make it easier to return Bs 13
549             arguments when there's probably only 2 you really care about.
550              
551             Returns everything else that getattr() should.
552              
553             =back
554              
555             =cut
556              
557             sub easy_getattr {
558             my ($mode, $size) = @_;
559            
560             return (
561             0, 0, # $dev, $ino,
562             $mode,
563             1, # $nlink, see fuse.sourceforge.net/wiki/index.php/FAQ
564             $uid, $gid, # $uid, $gid,
565             0, # $rdev,
566             $size, # $size,
567             $ctime, $ctime, $ctime, # actually $atime, $mtime, $ctime,
568             1024, 1, # $blksize, $blocks,
569             );
570             }
571              
572             =head1 FUSE FILESYSTEM FUNCTIONS
573              
574             These can be overridden if you really want to get at the guts of the
575             filesystem, but if you really wanted to get that dirty, you probably
576             wouldn't be using Fuse::Simple, would you?
577              
578             =over
579              
580             =item B()
581              
582             return ENOSYS "Function not implemented" to the program that's
583             accessing this function.
584              
585             =begin testing
586              
587             is(fs_not_imp(), -38, "fs_not_imp -38");
588              
589             =end testing
590              
591             =cut
592              
593             sub fs_not_imp { return -ENOSYS() }
594              
595             =item B(I<$path>)
596              
597             =begin testing
598              
599             is(fs_flush(), 0, "fs_flush");
600              
601             =end testing
602              
603             =cut
604              
605             sub fs_flush {
606             # we're passed a path, but finding my coderef stuff from a path
607             # is a bit of a 'mare. flush the lot, won't hurt TOO much.
608             print "Flushing\n" if $debug;
609             %codecache = ();
610             return 0;
611             }
612              
613             =item B(I<$path>)
614              
615             =cut
616              
617             sub fs_getattr {
618             my $path = shift;
619             my $obj = fetch($path);
620            
621             # undef doesn't actually mean "file not found", it could be a coderef
622             # file-sub which has returned undef.
623             return easy_getattr(S_IFREG | 0200, 0) unless defined($obj);
624            
625             switch (ref($obj)) {
626             case "ERROR" { # this is an error to be returned.
627             return -$$obj;
628             }
629             case "" { # this isn't a ref, it's a real string "file"
630             return easy_getattr(S_IFREG | 0644, length($obj));
631             }
632             # case "CODE" should never happen - already been run by fetch()
633             case "HASH" { # this is a directory hash
634             return easy_getattr(S_IFDIR | 0755, 1);
635             }
636             case "SCALAR" { # this is a scalar ref. we use these for symlinks.
637             return easy_getattr(S_IFLNK | 0777, 1);
638             }
639             else { # what the hell is this file?!?
640             print "+++ What on earth is ",ref($obj)," $path ?\n";
641             return easy_getattr(S_IFREG | 0000, 0);
642             }
643             }
644             }
645              
646             =item B(I<$path>)
647              
648             =cut
649              
650             sub fs_getdir {
651             my $obj = fetch(shift);
652             return -$$obj if ref($obj) eq "ERROR"; # THINK this is a good idea.
653             return -ENOENT() unless ref($obj) eq "HASH";
654             return (".", "..", sort(keys %$obj), 0);
655             }
656              
657             =item B(I<$path, $flags>)
658              
659             =cut
660              
661             sub fs_open {
662             # doesn't really need to open, just needs to check.
663             my $obj = fetch(shift);
664             my $flags = shift;
665             dump_open_flags($flags) if $debug;
666            
667             # if it's undefined, and we're not writing to it, return an error
668             return -EBADF() unless defined($obj) or ($flags & O_ACCMODE());
669            
670             switch (ref($obj)) {
671             case "ERROR" { return -$$obj; }
672             case "" { return 0 } # this is a real string "file"
673             case "HASH" { return -EISDIR(); } # this is a directory hash
674             else { return -ENOSYS(); } # what the hell is this file?!?
675             }
676             }
677              
678             =item B(I<$path, $size, $offset>)
679              
680             =cut
681              
682             sub fs_read {
683             my $obj = fetch(shift);
684             my $size = shift;
685             my $off = shift;
686            
687             return -ENOENT() unless defined($obj);
688             return -$$obj if ref($obj) eq "ERROR";
689             # any other types of refs are probably bad
690             return -ENOENT() if ref($obj);
691            
692             if ($off > length($obj)) {
693             return -EINVAL();
694             } elsif ($off == length($obj)) {
695             return 0; # EOF
696             }
697             return substr($obj, $off, $size);
698             }
699              
700             =item B(I<$path>)
701              
702             =cut
703              
704             sub fs_readlink {
705             my $obj = fetch(shift);
706             return -$$obj if ref($obj) eq "ERROR";
707             return -EINVAL() unless ref($obj) eq "SCALAR";
708             return $$obj;
709             }
710              
711             =item B(I<$path, $flags>)
712              
713             =cut
714              
715             sub fs_release {
716             my ($path, $flags) = @_;
717             dump_open_flags($flags) if $debug;
718             return 0;
719             }
720              
721             =item B()
722              
723             =cut
724              
725             sub fs_statfs {
726             return (
727             255, # $namelen,
728             1,1, # $files, $files_free,
729             1,1, # $blocks, $blocks_avail, # 0,0 seems to hide it from df?
730             2, # $blocksize,
731             );
732             }
733              
734             =item B(I<$path, $offset>)
735              
736             =cut
737              
738             sub fs_truncate {
739             my $obj = fetch(shift, ""); # run anything to set it to ""
740             return -$$obj if ref($obj) eq "ERROR";
741             return 0;
742             }
743              
744             =item B(I<$path, $buffer, $offset>)
745              
746             =cut
747              
748             sub fs_write {
749             my ($path, $buf, $off) = @_;
750             my $obj = fetch($path, $buf, $off); # this runs the coderefs!
751             return -$$obj if ref($obj) eq "ERROR";
752             return length($buf);
753             }
754              
755             1; # for use() or require()
756              
757             __END__