Fuse-0.16.1/0000755000175000017500000000000012235014036011463 5ustar demondemonFuse-0.16.1/test.pl0000644000175000017500000000037511566236206013017 0ustar demondemon#!/usr/bin/perl BEGIN { $ENV{HARNESS_IGNORE_EXITCODE} = 1; } use Test::Harness qw(&runtests $verbose); $verbose=0; die "cannot find test directory!" unless -d "test"; my (@files) = ; runtests("test/s/mount.t",sort(@files),"test/s/umount.t"); Fuse-0.16.1/test/0000755000175000017500000000000012235014036012442 5ustar demondemonFuse-0.16.1/test/unlink.t0000644000175000017500000000052611615316511014136 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 4; chdir($_point); open($file, '>', 'file'); close($file); ok(-f "file","file exists"); chdir($_real); ok(-f "file","file really exists"); chdir($_point); unlink("file"); ok(! -f "file","file unlinked"); chdir($_real); ok(! -f "file","file really unlinked"); Fuse-0.16.1/test/link.t0000644000175000017500000000074211615316666013606 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 8; chdir($_point); open($file, '>', 'womble'); print $file "hippity\n"; close($file); ok(-f "womble","exists"); ok(!-f "rabbit","target file doesn't exist"); is(-s "womble",8,"right size"); ok(link("womble","rabbit"),"link"); ok(-f "womble","old file exists"); ok(-f "rabbit","target file exists"); is(-s "womble",8,"right size"); is(-s "rabbit",8,"right size"); unlink("womble"); unlink("rabbit"); Fuse-0.16.1/test/read.t0000644000175000017500000000052111615316560013550 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 3; chdir($_real); open($file, '>','file'); print $file "frog\n"; close($file); chdir($_point); ok(open(FILE,"file"),"open"); my ($data) = ; close(FILE); is(length($data),5,"right amount read"); is($data,"frog\n","right data read"); unlink("file"); Fuse-0.16.1/test/truncate.t0000644000175000017500000000053111615316546014467 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 5; chdir($_point); open($file, '>', 'womble'); print $file "hippity\n"; close($file); ok(-f "womble","exists"); is(-s "womble",8,"right size"); ok(truncate("womble",4),"truncate"); ok(-f "womble","file exists"); is(-s "womble",4,"right size"); unlink("womble"); Fuse-0.16.1/test/getattr.t0000644000175000017500000000321111615554061014306 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; use Data::Dumper; plan tests => 203; $scale = 1024 * 1024 * 1024; if ($^O eq 'darwin') { $scale = 1024 * 1024; } sub test_file { my $size = shift; my ($a, $b) = ("$_real/wibble-$size","$_point/wibble-$size"); # diag "test $size Gb file"; open(my $fh, '>', $a) || die "can't open $b: $!"; seek($fh, $size * $scale, 0); print $fh ' '; close($fh); # diag "size $b = ",-s $b, " $a = ", -s $a; is(-A "$a", -A "$b", '-A'); # 1 is(-B "$a", -B "$b", '-B'); # 2 is(-C "$a", -C "$b", '-C'); # 3 is(-M "$a", -M "$b", '-M'); # 4 is(-O "$a", -O "$b", '-O'); # 5 is(-R "$a", -R "$b", '-R'); # 6 is(-S "$a", -S "$b", '-S'); # 7 is(-T "$a", -T "$b", '-T'); # 8 is(-W "$a", -W "$b", '-W'); # 9 is(-X "$a", -X "$b", '-X'); # 10 is(-b "$a", -b "$b", '-b'); # 11 is(-c "$a", -c "$b", '-c'); # 12 is(-d "$a", -d "$b", '-d'); # 13 is(-e "$a", -e "$b", '-e'); # 14 is(-f "$a", -f "$b", '-f'); # 15 is(-g "$a", -g "$b", '-g'); # 16 is(-k "$a", -k "$b", '-k'); # 17 is(-l "$a", -l "$b", '-l'); # 18 is(-o "$a", -o "$b", '-o'); # 19 is(-p "$a", -p "$b", '-p'); # 20 is(-r "$a", -r "$b", '-r'); # 21 is(-s "$a", -s "$b", '-s'); # 22 is(-t "$a", -t "$b", '-t'); # 23 is(-u "$a", -u "$b", '-u'); # 24 is(-w "$a", -w "$b", '-w'); # 25 is(-x "$a", -x "$b", '-x'); # 26 is(-z "$a", -z "$b", '-z'); # 27 my (@astat, @bstat); @astat = stat("$a"); @bstat = stat("$b"); # dev, inode and blksize can legally change @astat = @astat[2..10,12]; @bstat = @bstat[2..10,12]; is(join(" ",@astat),join(" ",@bstat),"stat()"); ok( unlink($a), 'unlink' ); } test_file( $_ ) foreach ( 1, 2, 4, 8, 16, 32, 64 ); Fuse-0.16.1/test/test-template0000644000175000017500000000013511566236206015167 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 1; ok(1); Fuse-0.16.1/test/pod.t0000644000175000017500000000025611566236206013427 0ustar demondemon#!/usr/bin/perl use strict; use warnings; use Test::More; eval "use Test::Pod 1.00"; plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; all_pod_files_ok(); Fuse-0.16.1/test/open.t0000644000175000017500000000035611615316600013577 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 1; chdir($_real); open($file, '>', 'file'); print $file "frog\n"; close($file); chdir($_point); ok(open(FILE,"file"),"open"); close(FILE); unlink("file"); Fuse-0.16.1/test/write.t0000644000175000017500000000203111615316611013762 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 15; my ($data); chdir($_point); undef $/; # slurp it all # create file open($file, '>', 'writefile'); print $file "frogbing\n"; close($file); # fetch contents of file ok(open(FILE,"writefile"),"open"); $data = ; close(FILE); is(length($data),9,"right amount read"); is($data,"frogbing\n","right data read"); # overwrite part ok(open(FILE,'+<',"writefile"),"open"); ok(seek(FILE,2,0),"seek"); ok(print(FILE "ib"),"print"); close(FILE); # fetch contents of file ok(open(FILE,"writefile"),"open"); $data = ; close(FILE); is(length($data),9,"right amount read"); is($data,"fribbing\n","right data read"); # overwrite part, append some ok(open(FILE,'+<',"writefile"),"open"); ok(seek(FILE,7,0),"seek"); ok(print(FILE "gle"),"print"); close(FILE); # fetch contents of file ok(open(FILE,"writefile"),"open"); $data = ; close(FILE); is(length($data),10,"right amount read"); is($data,"fribbingle","right data read"); # kill file unlink("writefile"); Fuse-0.16.1/test/mknod.t0000644000175000017500000000323611615552453013756 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 24; use English; use Unix::Mknod qw(:all); use Fcntl qw(S_IFCHR S_IFBLK); use POSIX; my (@stat); chdir($_point); ok(open($file, '>', 'reg'),"create normal file"); close($file); ok(defined mkfifo($_point.'/fifo', 0600),"create fifo"); chdir($_real); ok(-e "reg" ,"normal file exists"); ok(-e "fifo","fifo exists"); ok(-f "reg" ,"normal file is normal file"); ok(-p "fifo","fifo is fifo"); SKIP: { skip('Need root to mknod devices', 8) unless ($UID == 0); chdir($_point); ok(!mknod($_point.'/chr', 0600|S_IFCHR, makedev(2,3)),"create chrdev"); ok(!mknod($_point.'/blk', 0600|S_IFBLK, makedev(2,3)),"create blkdev"); chdir($_real); ok(-e "chr" ,"chrdev exists"); ok(-e "blk" ,"blkdev exists"); skip('mknod() is just pretend under fakeroot(1)', 4) if exists $ENV{FAKEROOTKEY}; ok(-c "chr" ,"chrdev is chrdev"); ok(-b "blk" ,"blkdev is blkdev"); @stat = stat("chr"); is($stat[6],makedev(2,3),"chrdev has right major,minor"); @stat = stat("blk"); is($stat[6],makedev(2,3),"blkdev has right major,minor"); } chdir($_point); ok(-e "reg" ,"normal file exists"); ok(-e "fifo","fifo exists"); ok(-f "reg" ,"normal file is normal file"); ok(-p "fifo","fifo is fifo"); SKIP: { skip('Need root to mknod devices', 6) unless ($UID == 0); ok(-e "chr" ,"chrdev exists"); ok(-e "blk" ,"blkdev exists"); ok(-c "chr" ,"chrdev is chrdev"); ok(-b "blk" ,"blkdev is blkdev"); @stat = stat("chr"); is($stat[6],makedev(2,3),"chrdev has right major,minor"); @stat = stat("blk"); is($stat[6],makedev(2,3),"blkdev has right major,minor"); } map { unlink } qw(reg chr blk fifo); Fuse-0.16.1/test/chown.t0000644000175000017500000000074411615316766013772 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; use English; plan tests => 4; my (@stat); chdir($_point); open($file, '>', 'file'); print $file "frog\n"; close($file); SKIP: { skip('Need root to give away ownership', 4) unless ($UID == 0); ok(chown(0,0,"file"),"set 0,0"); @stat = stat("file"); ok($stat[4] == 0 && $stat[5] == 0,"0,0"); ok(chown(1,1,"file"),"set 1,1"); @stat = stat("file"); ok($stat[4] == 1 && $stat[5] == 1,"1,1"); } unlink("file"); Fuse-0.16.1/test/readlink.t0000644000175000017500000000045411566236206014436 0ustar demondemon#!/usr/bin/perl use test::helper qw($_point $_real); use Test::More; plan tests => 4; chdir($_real); ok(symlink("abc","def"),"OS supports symlinks"); is(readlink("def"),"abc","OS supports symlinks"); chdir($_point); ok(-l "def","symlink exists"); is(readlink("def"),"abc","readlink"); unlink("def"); Fuse-0.16.1/test/getdir.t0000644000175000017500000000162711615552007014121 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; my (@names) = qw(abc def ghi jkl mno pqr stu jlk sfdaljk sdfakjlsdfa kjldsf kjl;sdf akjl;asdf klj;asdf lkjsdflkjsdfkjlsdfakjsdfakjlsadfkjl;asdfklj;asdfkjl;asdfklj;asdfkjl;asdfkjlasdflkj;sadf); @names = sort(@names); plan tests => 2 * scalar @names; chdir($_real); # create entries foreach $fname (@names) { open($file, '>', $fname); close($file); } # make sure they exist in real dir opendir(REAL,$_real); my (@ents) = readdir(REAL); closedir(REAL); @ents = sort(@ents); map { shift(@ents) while($ents[0] eq '.' || $ents[0] eq '..'); is(shift(@ents),$_,"ent $_") } @names; # make sure they exist in fuse dir opendir(POINT,$_point); @ents = readdir(POINT); closedir(POINT); @ents = sort(@ents); map { shift(@ents) while($ents[0] eq '.' || $ents[0] eq '..'); is(shift(@ents),$_,"ent $_") } @names; # remove them map { unlink } @names; Fuse-0.16.1/test/rmdir.t0000644000175000017500000000045411566236206013762 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 5; chdir($_real); ok(mkdir("dir"),"mkdir"); ok(-d "dir","dir really exists"); chdir($_point); ok(-d "dir","dir exists"); rmdir("dir"); ok(! -d "dir","dir removed"); chdir($_real); ok(! -d "dir","dir really removed"); Fuse-0.16.1/test/s/0000755000175000017500000000000012235014036012704 5ustar demondemonFuse-0.16.1/test/s/mount.t0000644000175000017500000000213712214120160014227 0ustar demondemon#!/usr/bin/perl -w use test::helper qw($_point $_loop $_opts $_real $_pidfile); use strict; use Errno qw(:POSIX); use Test::More tests => 3; sub is_mounted { my $diag = -e '/proc/mounts' ? `cat /proc/mounts` : ($^O eq 'linux' ? `/bin/mount` : ($^O eq 'solaris' ? `/usr/sbin/mount` : `/sbin/mount`)); my $pattern = $^O eq 'solaris' ? qr{^$_point }m : qr{ (?:/private)?$_point }; return $diag =~ $pattern; } ok(!is_mounted(),"already mounted"); ok(-f $_loop,"loopback exists"); mkdir $_point; mkdir $_real; diag "mounting $_loop to $_point"; open REALSTDOUT, '>&STDOUT'; open REALSTDERR, '>&STDERR'; open STDOUT, '>', '/dev/null'; open STDERR, '>&', \*STDOUT; system("perl -Iblib/lib -Iblib/arch $_loop $_opts $_point"); open STDOUT, '>&', \*REALSTDOUT; open STDERR, '>&', \*REALSTDERR; my ($success, $count) = (0,0); while ($count++ < 50 && !$success) { select(undef, undef, undef, 0.1); ($success) = is_mounted(); } diag "Mounted in ", $count/10, " secs"; ok($success,"mount succeeded"); system("rm -rf $_real"); unless($success) { kill('INT',`cat $_pidfile`); unlink($_pidfile); } else { mkdir($_real); } Fuse-0.16.1/test/s/umount.t0000644000175000017500000000047711566236206014443 0ustar demondemon#!/usr/bin/perl use test::helper qw($_point $_real $_pidfile); use strict; use Test::More tests => 1; use POSIX qw(WEXITSTATUS); system("fusermount -u $_point"); if(POSIX::WEXITSTATUS($?) != 0) { system("umount $_point"); } ok(POSIX::WEXITSTATUS($?) == 0,"unmount"); system("rm -rf $_real $_pidfile"); rmdir($_point); Fuse-0.16.1/test/chmod.t0000644000175000017500000000050311615316657013736 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 4; chdir($_point); open($file, '>', 'file'); print $file "frog\n"; close($file); ok(chmod(0644,"file"),"set unexecutable"); ok(!-x "file","unexecutable"); ok(chmod(0755,"file"),"set executable"); ok(-x "file","executable"); unlink("file"); Fuse-0.16.1/test/rename.t0000644000175000017500000000061612214632443014106 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; if ($^O eq 'openbsd') { exit(); } plan tests => 5; chdir($_point); open($file, '>', 'frog'); print $file "hippity\n"; close($file); ok(-f "frog","exists"); ok(!-f "toad","target file doesn't exist"); ok(rename("frog","toad"),"rename"); ok(!-f "frog","old file doesn't exist"); ok(-f "toad","target file exists"); unlink("toad"); Fuse-0.16.1/test/mkdir.t0000644000175000017500000000035211566236206013750 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 3; chdir($_point); ok(mkdir("dir"),"mkdir"); ok(-d "dir","dir exists"); chdir($_real); ok(-d "dir","dir really exists"); chdir($_point); rmdir("dir"); Fuse-0.16.1/test/symlink.t0000644000175000017500000000133712214632443014326 0ustar demondemon#!/usr/bin/perl use test::helper qw($_point $_real); use Test::More; plan tests => 6; chdir($_point); ok(symlink("abc","def"),"symlink created"); ok(-l "def","symlink exists"); is(readlink("def"),"abc","it worked"); chdir($_real); ok(-l "def","symlink really exists"); is(readlink("def"),"abc","really worked"); unlink("def"); # bug: doing a 'cp -a' on a directory which contains a symlink # reports an error mkdir("dira"); open($file, '>', 'dira/filea'); close($file); symlink('filea', 'dira/fileb'); my $cp = 'cp -a'; if ($^O eq 'netbsd' || $^O eq 'openbsd') { $cp = 'cp -R'; } is(system($cp . " dira dirb")>>8,0,$cp); map { unlink($_) } ('dira/filea', 'dira/fileb', 'dirb/filea', 'dirb/fileb'); map { rmdir($_) } ('dira', 'dirb'); Fuse-0.16.1/test/statfs.t0000644000175000017500000000126711723447270014154 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; use Config; use Filesys::Statvfs; if ($^O eq 'netbsd') { # Ignoring the f_namelen field; NetBSD's statvfs1(2) syscall doesn't # seem to handle f_namelen right for PUFFS-based filesystems. Not our # failure, and mostly irrelevant. plan tests => 6; } else { plan tests => 7; } ok(my @list = (statvfs($_point))[1,2,3,5,6,9]); diag "statfs: ",join(', ', @list); is(shift(@list),4096,'block size'); is(shift(@list),1000000,'blocks'); is(shift(@list),500000,'blocks free'); is(shift(@list),1000000,'files'); is(shift(@list),500000,'files free'); unless ($^O eq 'netbsd') { is(shift(@list),255,'namelen'); } Fuse-0.16.1/test/utime.t0000644000175000017500000000047111615316537013770 0ustar demondemon#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 3; my (@stat); chdir($_real); open($file, '>', 'file'); print($file "frog\n"); close($file); chdir($_point); ok(utime(1,2,"file"),"set utime"); @stat = stat("file"); is($stat[8],1,"atime"); is($stat[9],2,"mtime"); unlink("file"); Fuse-0.16.1/test/helper.pm0000644000175000017500000000206512214120160014253 0ustar demondemon#!/usr/bin/perl package # avoid cpan indexing test::helper; use strict; use Exporter; use Config; use POSIX qw(WEXITSTATUS); our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS); @ISA = "Exporter"; @EXPORT_OK = qw($_loop $_opts $_point $_pidfile $_real); my $tmp = -d '/private' ? '/private/tmp' : '/tmp'; our($_loop, $_point, $_pidfile, $_real, $_opts) = ('examples/loopback.pl',"$tmp/fusemnt-".$ENV{LOGNAME},$ENV{'PWD'} . "/test/s/mounted.pid","$tmp/fusetest-".$ENV{LOGNAME}, ''); $_opts = ' --pidfile ' . $_pidfile; $_opts .= ' --logfile /tmp/fusemnt.log'; $_opts .= $Config{useithreads} ? ' --use-threads' : ''; if($0 !~ qr|s/u?mount\.t$|) { my ($reject) = 1; if(open my $fh, '<', $_pidfile) { my $pid = do {local $/ = undef; <$fh>}; close $fh; if(kill 0, $pid) { my $pattern = $^O eq 'solaris' ? qr{^$_point on }m : qr{on (?:/private)?$_point }; if(`mount` =~ $pattern) { $reject = 0; } else { kill 1, $pid; } } } system("ls $_point >/dev/null"); $reject = 1 if (POSIX::WEXITSTATUS($?)); die "not properly mounted\n" if $reject; } 1; Fuse-0.16.1/MANIFEST0000644000175000017500000000121711723447270012630 0ustar demondemonAUTHORS Changes Fuse.pm Fuse.xs Makefile.PL MANIFEST README test.pl META.yml Module meta-data (added by MakeMaker) test/helper.pm test/s/umount.t test/s/mount.t test/test-template test/chmod.t test/chown.t test/getattr.t test/getdir.t test/link.t test/mkdir.t test/mknod.t test/open.t test/readlink.t test/read.t test/rename.t test/rmdir.t test/statfs.t test/symlink.t test/truncate.t test/unlink.t test/utime.t test/write.t test/pod.t examples/example.pl examples/example_t.pl examples/fioc.pl examples/fioclient.pl examples/fsel.pl examples/fselclient.pl examples/loopback.pl examples/rmount_remote.pl examples/rmount.pl Fuse-0.16.1/Changes0000644000175000017500000001154012235012715012761 0ustar demondemonRevision history for Perl module Fuse 0.16.1 2013-10-31 - Fix missing "temp" variable declaration in _PLfuse_release, which only affects 32-bit systems using FUSE 2.9.0 or later. 0.16 2013-09-15 - Fix unbounded stack growth due to previous changes in getdir() and readdir() wrappers that didn't properly decrement SP. [RT #77321] - Implement support for FUSE 2.9 specific operations. (flock, read_buf, write_buf, fallocate) - Implement helper functions (fuse_buf_size, fuse_buf_copy) for handling generic buffers inside read_buf and write_buf wrappers. - Make the write() wrapper do less data copying. - Improve compatibility with OpenIndiana (an open flavor of Solaris). - Add support for OpenBSD's flavor of FUSE. 0.15 2013-06-08 - Eliminate more uses of system() in tests. - Enable the ioctl() operation when built against FUSE 2.8 or later. Also wrote tests based off fioc.c and fioclient.c from FUSE. - Use smaller getattr test sizes only on MacOS X. - Permanently fix the XATTR_{CREATE,REPLACE} symbols. - Add a wrapper for the poll() operation when built against FUSE 2.8 or later. Also wrote tests based off fsel.c and fselclient.c from FUSE. - Fixed a thinko in the platform handling chain in Makefile.PL. - Added handling for sub-second [amc]time stamps. - Improve compatibility with Fuse4X. - Improve future compatibility with non-Linux FUSE 2.8 implementations. 0.14 2011-08-01 - Retooling portions of the test facilities, and removing dependence on syscall() and knowing syscall numbers for basic test functionality. - Compatibility fixes for Perl 5.8 and Perl 5.13/5.14 with threads. - Cleanups to build system, to use pkg-config to get the Fuse build arguments for building our code against the installed libfuse. Cleans up some of the mess before of different ways of handling different OSes; NetBSD is still a bit messy due to librefuse. - Decrease file sizes in getattr test, to keep test from running for multiple hours (due to HFS+ not supporting sparse files). 0.13 2011-07-03 - improved support for FreeBSD, NetBSD and OS X - restored non-threaded perl support 0.12 2011-05-20 - all changes in this version are contributed by Darrik Pates - BACKWARD COMPATILIBY CHANGE: readdir introduced in 0.11 changed! - mount option -o big_writes, and added: opendir, releasedir, fsyncdir, init, destroy, access, create, ftruncate, fgetattr, lock, utimens, bmap 0.11 2011-02-25 - make static callbacks thread-safe, contributed by Daniel Frett - readdir implmenentation contributed by Alex Sudakov RT #55953 0.10_1 2011-01-17 - cleanup options - 64 bit perl support submitted by Derrik Pates 0.09_4 2010-05-16 - Justin Fletcher addition of file handles on open files, RT #57517 0.09_3 2008-03-19 - really fix 2+ Gb file bug, RT #32639, RT #33903 0.09 2007-11-15 - support dh-make-perl with fakeroot - added fuse_get_context - works with MacFUSE http://code.google.com/p/macfuse/ - added example filter_attr_fs.pl 0.08 2006-11-29 14:24:39 CET - fix race condition in test/s/mount.t - allow Fuse to be run from a non-master thread - CPANPLUS doesn't report errors anymore if fuse isn't installed - fix to test helper 0.07 2005-12-25 10:37:00 PST - Remove the FUSE_DEBUG constant; we never actually implemented it to begin with. - "make test" now uses the version of Fuse you've just built, not the one installed in /usr/lib/perl5. - getattr test now allows blksize to vary between host and fuse fs, as this is not a bug. - Add experimental support for threading. The following minor API changes accommodate this: - The nonexistent (yet documented) "unthreaded=>1" attribute has been replaced with the "threaded=>1" attribute, and this time it actually exists. - Symbolic refs like "main::e_getattr" are now allowed for callbacks, because threaded mode needs to share() the callbacks, yet perl 5.8.7 does not allow share()ing code refs yet. Direct code-refs are still supported as much as possible (currently, non-threaded mode). - testsuite uses a multithreaded loopback.pl, when available. - Update docs accordingly. Update examples accordingly. - Works on FreeBSD with fuse4bsd http://fuse4bsd.creo.hu/ 0.06 2005-04-03 16:15:00 BST - Add support for operations supported by FUSE 2.2.1 (flush, release, fsync, extended attributes) - add mount options 0.04 2004-11-18 13:51:56 CET - new maintainer, Dobrica Pavlinusic - updated to work with current CVS version of fuse 0.03 2001-12-05 02:17:52 - changed getattr() to smell like perl's stat() - fleshed out the documentation a bit 0.02 2001-12-02 18:59:56 - works well enough to release, but still needs testing 0.01 2001-11-28 21:45:20 - original version; created by h2xs 1.21 with options include/fuse.h Fuse-0.16.1/Fuse.pm0000755000175000017500000007057712235012715012750 0ustar demondemonpackage Fuse; use 5.006; use strict; use warnings; use Errno; use Carp; use Config; use List::Util qw(sum); require Exporter; require DynaLoader; use AutoLoader; our @ISA = qw(Exporter DynaLoader); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use Fuse ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( 'all' => [ qw(FUSE_BUF_IS_FD FUSE_BUF_FD_SEEK FUSE_BUF_FD_RETRY UTIME_NOW UTIME_OMIT XATTR_CREATE XATTR_REPLACE fuse_get_context fuse_version fuse_buf_copy fuse_buf_size FUSE_IOCTL_COMPAT FUSE_IOCTL_UNRESTRICTED FUSE_IOCTL_RETRY FUSE_IOCTL_MAX_IOV notify_poll pollhandle_destroy) ], 'xattr' => [ qw(XATTR_CREATE XATTR_REPLACE) ], 'utime' => [ qw(UTIME_NOW UTIME_OMIT) ], 'zerocopy' => [ qw(FUSE_BUF_IS_FD FUSE_BUF_FD_SEEK FUSE_BUF_FD_RETRY) ], 'ioctl' => [ qw(FUSE_IOCTL_COMPAT FUSE_IOCTL_UNRESTRICTED FUSE_IOCTL_RETRY FUSE_IOCTL_MAX_IOV) ], ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = (); our $VERSION = '0.16.1'; sub AUTOLOAD { # This AUTOLOAD is used to 'autoload' constants from the constant() # XS function. If a constant is not found then control is passed # to the AUTOLOAD in AutoLoader. my $constname; our $AUTOLOAD; ($constname = $AUTOLOAD) =~ s/.*:://; croak "& not defined" if $constname eq 'constant'; my $val = constant($constname, @_ ? $_[0] : 0); if ($! != 0) { if ($!{EINVAL}) { $AutoLoader::AUTOLOAD = $AUTOLOAD; goto &AutoLoader::AUTOLOAD; } else { croak "Your vendor has not defined Fuse macro $constname"; } } { no strict 'refs'; # Fixed between 5.005_53 and 5.005_61 if ($] >= 5.00561) { *$AUTOLOAD = sub () { $val }; } else { *$AUTOLOAD = sub { $val }; } } goto &$AUTOLOAD; } bootstrap Fuse $VERSION; use constant FUSE_IOCTL_COMPAT => (1 << 0); use constant FUSE_IOCTL_UNRESTRICTED => (1 << 1); use constant FUSE_IOCTL_RETRY => (1 << 2); use constant FUSE_IOCTL_MAX_IOV => 256; sub main { my @names = qw(getattr readlink getdir mknod mkdir unlink rmdir symlink rename link chmod chown truncate utime open read write statfs flush release fsync setxattr getxattr listxattr removexattr opendir readdir releasedir fsyncdir init destroy access create ftruncate fgetattr lock utimens bmap); my ($fuse_vmajor, $fuse_vminor, $fuse_vmicro) = fuse_version(); my $fuse_version = $fuse_vmajor + ($fuse_vminor * 1.0 / 1_000) + ($fuse_vmicro * 1.0 / 1_000_000); if ($fuse_version >= 2.008) { # junk doesn't contain a function pointer, and hopefully # never will; it's a "dead" zone in the struct # fuse_operations where a flag bit is declared. we don't # need to concern ourselves with it, and it appears any # arch with a 64 bit pointer will align everything to # 8 bytes, making the question of pointer alignment for # the last 2 wrapper functions no big thing. push(@names, qw/junk ioctl poll/); } if ($fuse_version >= 2.009) { push(@names, qw/write_buf read_buf flock/); } if ($fuse_version >= 2.009001) { push(@names, qw/fallocate/); } my @subs = map {undef} @names; my $tmp = 0; my %mapping = map { $_ => $tmp++ } @names; my @otherargs = qw(debug threaded mountpoint mountopts nullpath_ok utimens_as_array nopath utime_omit_ok); my %otherargs = ( debug => 0, threaded => 0, mountpoint => "", mountopts => "", nullpath_ok => 0, utimens_as_array => 0, nopath => 0, utime_omit_ok => 0, ); while(my $name = shift) { my ($subref) = shift; if(exists($otherargs{$name})) { $otherargs{$name} = $subref; } else { croak "Usage: Fuse::main(getattr => \"main::my_getattr\", ...)" unless $subref; if (exists $mapping{$name}) { $subs[$mapping{$name}] = $subref; } else { carp "There is no function $name"; } } } if($otherargs{threaded}) { # make sure threads are both available, and loaded. if($Config{useithreads}) { if(exists($threads::{VERSION})) { if(exists($threads::shared::{VERSION})) { # threads will work. } else { carp("Thread support requires you to use threads::shared.\nThreads are disabled.\n"); $otherargs{threaded} = 0; } } else { carp("Thread support requires you to use threads and threads::shared.\nThreads are disabled.\n"); $otherargs{threaded} = 0; } } else { carp("Thread support was not compiled into this build of perl.\nThreads are disabled.\n"); $otherargs{threaded} = 0; } } perl_fuse_main(@otherargs{@otherargs},@subs); } sub fuse_buf_size { my ($buf) = @_; return sum(map { $_->{size} } @$buf); } # Autoload methods go after =cut, and are processed by the autosplit program. 1; __END__ =head1 NAME Fuse - write filesystems in Perl using FUSE =head1 SYNOPSIS use Fuse; my ($mountpoint) = ""; $mountpoint = shift(@ARGV) if @ARGV; Fuse::main(mountpoint=>$mountpoint, getattr=>"main::my_getattr", getdir=>"main::my_getdir", ...); =head1 DESCRIPTION This lets you implement filesystems in perl, through the FUSE (Filesystem in USErspace) kernel/lib interface. FUSE expects you to implement callbacks for the various functions. In the following definitions, "errno" can be 0 (for a success), -EINVAL, -ENOENT, -EONFIRE, any integer less than 1 really. You can import standard error constants by saying something like "use POSIX qw(EDOTDOT ENOANO);". Every constant you need (file types, open() flags, error values, etc) can be imported either from POSIX or from Fcntl, often both. See their respective documentations, for more information. =head2 EXPORTED SYMBOLS None by default. You can request all exportable symbols by using the tag ":all". You can request the extended attribute symbols by using the tag ":xattr". This will export XATTR_CREATE and XATTR_REPLACE. =head2 FUNCTIONS =head3 Fuse::main Takes arguments in the form of hash key=>value pairs. There are many valid keys. Most of them correspond with names of callback functions, as described in section 'FUNCTIONS YOUR FILESYSTEM MAY IMPLEMENT'. A few special keys also exist: =over 1 =item debug => boolean This turns FUSE call tracing on and off. Default is 0 (which means off). =item mountpoint => string The point at which to mount this filesystem. There is no default, you must specify this. An example would be '/mnt'. =item mountopts => string This is a comma separated list of mount options to pass to the FUSE kernel module. At present, it allows the specification of the allow_other argument when mounting the new FUSE filesystem. To use this, you will also need 'user_allow_other' in /etc/fuse.conf as per the FUSE documention mountopts => "allow_other" or mountopts => "" =item threaded => boolean This turns FUSE multithreading on and off. The default is 0, meaning your FUSE script will run in single-threaded mode. Note that single-threaded mode also means that you will not have to worry about reentrancy, though you will have to worry about recursive lookups. In single-threaded mode, FUSE holds a global lock on your filesystem, and will wait for one callback to return before calling another. This can lead to deadlocks, if your script makes any attempt to access files or directories in the filesystem it is providing. (This includes calling stat() on the mount-point, statfs() calls from the 'df' command, and so on and so forth.) It is worth paying a little attention and being careful about this. Enabling multithreading will cause FUSE to make multiple simultaneous calls into the various callback functions of your perl script. If you enable threaded mode, you can enjoy all the parallel execution and interactive response benefits of threads, and you get to enjoy all the benefits of race conditions and locking bugs, too. Please also ensure any other perl modules you're using are also thread-safe. (If enabled, this option will cause a warning if your perl interpreter was not built with USE_ITHREADS, or if you have failed to use threads or threads::shared.) =item nullpath_ok => boolean This flag tells Fuse to not pass paths for functions that operate on file or directory handles. This will yield empty path parameters for functions including read, write, flush, release, fsync, readdir, releasedir, fsyncdir, truncate, fgetattr and lock. If you use this, you must return file/directory handles from open, opendir and create. Default is 0 (off). Only effective on Fuse 2.8 and up; with earlier versions, this does nothing. =item utimens_as_array => boolean This flag causes timestamps passed via the utimens() call to be passed as arrays containing the time in seconds, and a second value containing the number of nanoseconds, instead of a floating point value. This allows for more precise times, as the normal floating point type used by Perl (double) loses accuracy starting at about tenths of a microsecond. =item nopath => boolean Flag indicating that the path need not be calculated for the following operations: read, write, flush, release, fsync, readdir, releasedir, fsyncdir, ftruncate, fgetattr, lock, ioctl and poll Closely related to nullpath_ok, but if this flag is set then the path will not be calculated even if the file wasn't unlinked. However the path can still be defined if it needs to be calculated for some other reason. Only effective on Fuse 2.9 and up. =item utime_omit_ok => boolean Flag indicating that the filesystem accepts special UTIME_NOW and UTIME_OMIT values in its C operation. If you wish to use these constants, make sure to include the ':utime' flag when including the Fuse module, or the ':all' flag. Only effective on Fuse 2.9 and up. =back =head3 Fuse::fuse_get_context use Fuse "fuse_get_context"; my $caller_uid = fuse_get_context()->{"uid"}; my $caller_gid = fuse_get_context()->{"gid"}; my $caller_pid = fuse_get_context()->{"pid"}; Access context information about the current Fuse operation. =head3 Fuse::fuse_version Indicates the Fuse version in use; more accurately, indicates the version of the Fuse API in use at build time. If called in scalar context, the version will be returned as a decimal value; i.e., for Fuse API v2.6, will return "2.6". If called in array context, an array will be returned, containing the major, minor and micro version numbers of the Fuse API it was built against. =head3 Fuse::fuse_buf_size Computes the total size of a buffer vector. Applicable for C and C operations. =head3 Fuse::fuse_buf_copy Copies data from one buffer vector to another. Primarily useful if a buffer vector contains multiple, fragmented chunks or if it contains an FD buffer instead of a memory buffer. Applicable for C. =head3 Fuse::notify_poll Only available if the Fuse module is built against libfuse 2.8 or later. Use fuse_version() to determine if this is the case. Calling this function with a pollhandle argument (as provided to the C operation implementation) will send a notification to the caller poll()ing for I/O operation availability. If more than one pollhandle is provided for the same filehandle, only use the latest; you *can* send notifications to them all, but it is unnecessary and decreases performance. ONLY supply poll handles fed to you through C to this function. Due to thread safety requirements, we can't currently package the pointer up in an object the way we'd like to to prevent this situation, but your filesystem server program may segfault, or worse, if you feed things to this function which it is not supposed to receive. If you do anyway, we take no responsibility for whatever Bad Things(tm) may happen. =head3 Fuse::pollhandle_destroy Only available if the Fuse module is built against libfuse 2.8 or later. Use fuse_version() to determine if this is the case. This function destroys a poll handle (fed to your program through C). When you are done with a poll handle, either because it has been replaced, or because a notification has been sent to it, pass it to this function to dispose of it safely. ONLY supply poll handles fed to you through C to this function. Due to thread safety requirements, we can't currently package the pointer up in an object the way we'd like to to prevent this situation, but your filesystem server program may segfault, or worse, if you feed things to this function which it is not supposed to receive. If you do anyway, we take no responsibility for whatever Bad Things(tm) may happen. =head2 FUNCTIONS YOUR FILESYSTEM MAY IMPLEMENT =head3 getattr Arguments: filename. Returns a list, very similar to the 'stat' function (see perlfunc). On error, simply return a single numeric scalar value (e.g. "return -ENOENT();"). FIXME: the "ino" field is currently ignored. I tried setting it to 0 in an example script, which consistently caused segfaults. Fields (the following was stolen from perlfunc(1) with apologies): ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = getattr($filename); Here are the meaning of the fields: 0 dev device number of filesystem 1 ino inode number 2 mode file mode (type and permissions) 3 nlink number of (hard) links to the file 4 uid numeric user ID of file's owner 5 gid numeric group ID of file's owner 6 rdev the device identifier (special files only) 7 size total size of file, in bytes 8 atime last access time in seconds since the epoch 9 mtime last modify time in seconds since the epoch 10 ctime inode change time (NOT creation time!) in seconds since the epoch 11 blksize preferred block size for file system I/O 12 blocks actual number of blocks allocated (The epoch was at 00:00 January 1, 1970 GMT.) If you wish to provide sub-second precision timestamps, they may be passed either as the fractional part of a floating-point value, or as a two-element array, passed as an array ref, with the first element containing the number of seconds since the epoch, and the second containing the number of nanoseconds. This provides complete time precision, as a floating point number starts losing precision at about a tenth of a microsecond. So if you really care about that sort of thing... =head3 readlink Arguments: link pathname. Returns a scalar: either a numeric constant, or a text string. This is called when dereferencing symbolic links, to learn the target. example rv: return "/proc/self/fd/stdin"; =head3 getdir Arguments: Containing directory name. Returns a list: 0 or more text strings (the filenames), followed by a numeric errno (usually 0). This is used to obtain directory listings. It's opendir(), readdir(), filldir() and closedir() all in one call. example rv: return ('.', 'a', 'b', 0); =head3 mknod Arguments: Filename, numeric modes, numeric device Returns an errno (0 upon success, as usual). This function is called for all non-directory, non-symlink nodes, not just devices. =head3 mkdir Arguments: New directory pathname, numeric modes. Returns an errno. Called to create a directory. =head3 unlink Arguments: Filename. Returns an errno. Called to remove a file, device, or symlink. =head3 rmdir Arguments: Pathname. Returns an errno. Called to remove a directory. =head3 symlink Arguments: Existing filename, symlink name. Returns an errno. Called to create a symbolic link. =head3 rename Arguments: old filename, new filename. Returns an errno. Called to rename a file, and/or move a file from one directory to another. =head3 link Arguments: Existing filename, hardlink name. Returns an errno. Called to create hard links. =head3 chmod Arguments: Pathname, numeric modes. Returns an errno. Called to change permissions on a file/directory/device/symlink. =head3 chown Arguments: Pathname, numeric uid, numeric gid. Returns an errno. Called to change ownership of a file/directory/device/symlink. =head3 truncate Arguments: Pathname, numeric offset. Returns an errno. Called to truncate a file, at the given offset. =head3 utime Arguments: Pathname, numeric actime, numeric modtime. Returns an errno. Called to change access/modification times for a file/directory/device/symlink. =head3 open Arguments: Pathname, numeric flags (which is an OR-ing of stuff like O_RDONLY and O_SYNC, constants you can import from POSIX), fileinfo hash reference. Returns an errno, a file handle (optional). No creation, or truncation flags (O_CREAT, O_EXCL, O_TRUNC) will be passed to open(). The fileinfo hash reference contains flags from the Fuse open call which may be modified by the module. The only fields presently supported are: direct_io (version 2.4 onwards) keep_cache (version 2.4 onwards) nonseekable (version 2.8 onwards) Your open() method needs only check if the operation is permitted for the given flags, and return 0 for success. Optionally a file handle may be returned, which will be passed to subsequent read, write, flush, fsync and release calls. =head3 read Arguments: Pathname, numeric requested size, numeric offset, file handle Returns a numeric errno, or a string scalar with up to $requestedsize bytes of data. Called in an attempt to fetch a portion of the file. =head3 write Arguments: Pathname, scalar buffer, numeric offset, file handle. You can use length($buffer) to find the buffersize. Returns length($buffer) if successful (number of bytes written). Called in an attempt to write (or overwrite) a portion of the file. Be prepared because $buffer could contain random binary data with NULs and all sorts of other wonderful stuff. =head3 statfs Arguments: none Returns any of the following: -ENOANO() or $namelen, $files, $files_free, $blocks, $blocks_avail, $blocksize or -ENOANO(), $namelen, $files, $files_free, $blocks, $blocks_avail, $blocksize =head3 flush Arguments: Pathname, file handle Returns an errno or 0 on success. Called to synchronise any cached data. This is called before the file is closed. It may be called multiple times before a file is closed. =head3 release Arguments: Pathname, numeric flags passed to open, file handle, flock_release flag (when built against FUSE 2.9 or later), lock owner ID (when built against FUSE 2.9 or later) Returns an errno or 0 on success. Called to indicate that there are no more references to the file. Called once for every file with the same pathname and flags as were passed to open. =head3 fsync Arguments: Pathname, numeric flags Returns an errno or 0 on success. Called to synchronise the file's contents. If flags is non-zero, only synchronise the user data. Otherwise synchronise the user and meta data. =head3 setxattr Arguments: Pathname, extended attribute's name, extended attribute's value, numeric flags (which is an OR-ing of XATTR_CREATE and XATTR_REPLACE Returns an errno or 0 on success. Called to set the value of the named extended attribute. If you wish to reject setting of a particular form of extended attribute name (e.g.: regexps matching user\..* or security\..*), then return - EOPNOTSUPP. If flags is set to XATTR_CREATE and the extended attribute already exists, this should fail with - EEXIST. If flags is set to XATTR_REPLACE and the extended attribute doesn't exist, this should fail with - ENOATTR. XATTR_CREATE and XATTR_REPLACE are provided by this module, but not exported by default. To import them: use Fuse ':xattr'; or: use Fuse ':all'; =head3 getxattr Arguments: Pathname, extended attribute's name Returns an errno, 0 if there was no value, or the extended attribute's value. Called to get the value of the named extended attribute. =head3 listxattr Arguments: Pathname Returns a list: 0 or more text strings (the extended attribute names), followed by a numeric errno (usually 0). =head3 removexattr Arguments: Pathname, extended attribute's name Returns an errno or 0 on success. Removes the named extended attribute (if present) from a file. =head3 opendir Arguments: Pathname of a directory Returns an errno, and a directory handle (optional) Called when opening a directory for reading. If special handling is required to open a directory, this operation can be implemented to handle that. =head3 readdir Arguments: Pathname of a directory, numeric offset, (optional) directory handle Returns a list of 0 or more entries, followed by a numeric errno (usually 0). The entries can be simple strings (filenames), or arrays containing an offset number, the filename, and optionally an array ref containing the stat values (as would be returned from getattr()). This is used to read entries from a directory. It can be used to return just entry names like getdir(), or can get a segment of the available entries, which requires using array refs and the 2- or 3-item form, with offset values starting from 1. If you wish to return the parameters to fill each entry's struct stat, but do not wish to do partial entry lists/entry counting, set the first element of each array to 0 always. Note that if this call is implemented, it overrides getdir() ALWAYS. =head3 releasedir Arguments: Pathname of a directory, (optional) directory handle Returns an errno or 0 on success Called when there are no more references to an opened directory. Called once for each pathname or handle passed to opendir(). Similar to release(), but for directories. Accepts a return value, but like release(), the response code will not propagate to any corresponding closedir() calls. =head3 fsyncdir Arguments: Pathname of a directory, numeric flags, (optional) directory handle Returns an errno or 0 on success. Called to synchronize any changes to a directory's contents. If flag is non-zero, only synchronize user data, otherwise synchronize user data and metadata. =head3 init Arguments: None. Returns (optionally) an SV to be passed as private_data via fuse_get_context(). =head3 destroy Arguments: (optional) private data SV returned from init(), if any. Returns nothing. =head3 access Arguments: Pathname, access mode flags Returns an errno or 0 on success. Determine if the user attempting to access the indicated file has access to perform the requested actions. The user ID can be determined by calling fuse_get_context(). See access(2) for more information. =head3 create Arguments: Pathname, create mask, open mode flags Returns errno or 0 on success, and (optional) file handle. Create a file with the path indicated, then open a handle for reading and/or writing with the supplied mode flags. Can also return a file handle like open() as part of the call. =head3 ftruncate Arguments: Pathname, numeric offset, (optional) file handle Returns errno or 0 on success Like truncate(), but on an opened file. =head3 fgetattr Arguments: Pathname, (optional) file handle Returns a list, very similar to the 'stat' function (see perlfunc). On error, simply return a single numeric scalar value (e.g. "return -ENOENT();"). Like getattr(), but on an opened file. =head3 lock Arguments: Pathname, numeric command code, hashref containing lock parameters, (optional) file handle Returns errno or 0 on success Used to lock or unlock regions of a file. Locking is handled locally, but this allows (especially for networked file systems) for protocol-level locking semantics to also be employed, if any are available. See the Fuse documentation for more explanation of lock(). The needed symbols for the lock constants can be obtained by importing Fcntl. =head3 utimens Arguments: Pathname, last accessed time, last modified time Returns errno or 0 on success Like utime(), but allows time resolution down to the nanosecond. By default, times are passed as "numeric" (internally these are typically represented as "double"), so the sub-second portion is represented as fractions of a second. If you want times passed as arrays instead of floating point values, for higher precision, you should pass the C option to C. Note that if this call is implemented, it overrides utime() ALWAYS. =head3 bmap Arguments: Pathname, numeric blocksize, numeric block number Returns errno or 0 on success, and physical block number if successful Used to map a block number offset in a file to the physical block offset on the block device backing the file system. This is intended for filesystems that are stored on an actual block device, with the 'blkdev' option passed. =head3 ioctl Arguments: Pathname, ioctl command code, flags, data if ioctl op is a write, (optional) file handle Returns errno or 0 on success, and data if ioctl op is a read Used to handle ioctl() operations on files. See ioctl(2) for more information on the fine details of ioctl operation numbers. May need to h2ph system headers to get the necessary macros; keep in mind the macros are highly OS-dependent. Keep in mind that read and write are from the client perspective, so read from our end means data is going *out*, and write means data is coming *in*. It can be slightly confusing. =head3 poll Arguments: Pathname, poll handle ID (or undef if none), event mask, (optional) file handle Returns errno or 0 on success, and updated event mask on success Used to handle poll() operations on files. See poll(2) to learn more about event polling. Use IO::Poll to get the POLLIN, POLLOUT, and other symbols to describe the events which can happen on the filehandle. Save the poll handle ID to be passed to C and C functions, if it is not undef. Threading will likely be necessary for this operation to work. There is not an "out of band" data transfer channel provided as part of FUSE, so POLLPRI/POLLRDBAND/POLLWRBAND won't work. Poll handle is currently a read-only scalar; we are investigating a way to make this an object instead. =head3 write_buf Arguments: Pathname, offset, buffer vector, (optional) file handle. Write contents of buffer to an open file. Similar to the C method, but data is supplied in a generic buffer. Use fuse_buf_copy() to transfer data to the destination if necessary. =head3 read_buf Arguments: Pathname, size, offset, buffer vector, (optional) file handle. Store data from an open file in a buffer. Similar to the C method, but data is stored and returned in a generic buffer. No actual copying of data has to take place, the source file descriptor may simply be placed in the 'fd' member of the buffer access hash (and the 'flags' member OR'd with FUSE_BUF_IS_FD) for later retrieval. Also, if the FUSE_BUF_FD_SEEK constant is OR'd with 'flags', the 'pos' member should contain the offset (in bytes) to seek to in the file descriptor. If data is to be read, the read data should be placed in the 'mem' member of the buffer access hash, and the 'size' member should be updated if less data was read than requested. =head3 flock Arguments: pathname, (optional) file handle, unique lock owner ID, operation ID Perform BSD-style file locking operations. Operation ID will be one of LOCK_SH, LOCK_EX or LOCK_UN. Non-blocking lock requests will be indicated by having LOCK_NB OR'd into the value. For more information, see the flock(2) manpage. For the lock symbols, do: use Fcntl qw(flock); Locking is handled locally, but this allows (especially for networked file systems) for protocol-level locking semantics to also be employed, if any are available. =head3 fallocate Arguments: pathname, (optional) file handle, mode, offset, length Allocates space for an open file. This function ensures that required space is allocated for specified file. If this function returns success then any subsequent write request to specified range is guaranteed not to fail because of lack of space on the file system media. =head1 EXAMPLES There are a few example scripts in the examples/ subdirectory. These are: example.pl A simple "Hello world" type of script loopback.pl A filesystem loopback-device. like fusexmp from the main FUSE dist, it simply recurses file operations into the real filesystem. Unlike fusexmp, it only re-shares files under the /tmp/test directory. rmount.pl An NFS-workalike which tunnels through SSH. It requires an account on some ssh server (obviously), with public-key authentication enabled. (if you have to type in a password, you don't have this. man ssh_keygen.). Copy rmount_remote.pl to your home directory on the remote machine and make it executable. Then create a mountpoint subdir somewhere local, and run the example script: ./rmount.pl host /remote/dir /local/dir rmount_remote.pl A ripoff of loopback.pl meant to be used as a backend for rmount.pl. =head1 AUTHOR Mark Glines, Emark@glines.orgE =head1 SEE ALSO L, the FUSE documentation. =cut Fuse-0.16.1/README0000644000175000017500000001676012234560506012364 0ustar demondemonFuse perl bindings ================== Fuse is combination of Linux kernel module and user space library which enables you to write user-space filesystems. This module enables you to write filesystems using perl. Additional file-systems using Fuse module are released on CPAN using Fuse:: namespace. Currently that includes only Fuse::DBI which allows you to mount database as file system, but there will be more. This is a pre-production release. It seems to work quite well. In fact, I can't find any problems with it whatsoever. If you do, I want to know. INSTALLATION To install this module type the standard commands as root: perl Makefile.PL make make test make install DEPENDENCIES This module requires the FUSE C library and the FUSE kernel module. See http://fuse.sourceforge.net/ If you intend to use FUSE in threaded mode, you need a version of Perl which has been compiled with USE_ITHREADS. Then, you need to use threads and threads::shared. COPYRIGHT AND LICENCE This is contributed to the FUSE project by Mark Glines , and is therefore subject to the same license and copyright as FUSE itself. Please see the AUTHORS and COPYING files from the FUSE distribution for more information. EXAMPLES There are a few example scripts. You can find them in the examples/ subdirectory. These are: * example.pl, a simple "Hello world" type of script * loopback.pl, a filesystem loopback-device. like fusexmp from the main FUSE dist, it simply recurses file operations into the real filesystem. Unlike fusexmp, it only re-shares files under the /tmp/test directory. * rmount.pl, an NFS-workalike which tunnels through SSH. It requires an account on some ssh server (obviously), with public-key authentication enabled. (if you have to type in a password, you don't have this. man ssh_keygen.). Copy rmount_remote.pl to your home directory on the remote machine, and create a subdir somewhere, and then run it like: ./rmount.pl host /remote/dir /local/dir * rmount_remote.pl, a ripoff of loopback.pl meant to be used as a backend for rmount.pl. BUGS Currently tests have been attempted and succeeded on: * Ubuntu 13.04/amd64 * Fedora 19/amd64 * CentOS 6.4/amd64 * CentOS 5.9/amd64 * CentOS 5.9/i386 * OpenSuSE 12.3/amd64 * Debian 7.1/powerpc * Slackware 13.1/amd64 * NetBSD 6.1.1/amd64 * FreeBSD 9.1/amd64 * MacOS X 10.6.8 [OSXFUSE 2.6.1] * MacOS X 10.7.5 [OSXFUSE 2.6.1] NOTES FOR BSD USERS On NetBSD, there is a potential issue with readdir() only being called once when using librefuse. However, currently using Perfuse causes other issues (readlink() drops the last character from the read link path, and the block count in stat() is incorrect). We will be addressing these concerns with the appropriate developers in the near future. If you are using Perfuse on NetBSD, you should do the following (as root): cat >> /etc/sysctl.conf <<_EOT_ kern.sbmax=2621440 net.inet.tcp.sendbuf_max=2621440 net.inet6.tcp6.sendbuf_max=2621440 _EOT_ sysctl -f /etc/sysctl.conf Perfuse uses TCP sockets, and needs large send buffers. On NetBSD and FreeBSD, extended attributes do not work. These are specifically related to the FUSE implementations on those platforms. Normally you can not mount FUSE filesystems as non-root users on FreeBSD and NetBSD. They can allow non-root users to mount FUSE filesystems, but instead of changing the mode of /dev/fuse or /bin/fusermount, you need to use sysctl to allow user mounts. For FreeBSD, this involves (as root): sysctl -w vfs.usermount=1 pw usermod -G operator And on NetBSD (also as root): sysctl -w vfs.generic.usermount=1 chmod 0660 /dev/putter usermod -G wheel NOTES FOR OPENBSD IN PARTICULAR Oh, hello. You're an OpenBSD user. You like your secure, minimalist OS, but you really want in on that FUSE-y goodness. Well, it's still pretty early and kind of broken, but yes, FUSE has actually made its way onto OpenBSD. That said, it's not (as of this writing) in a released version. It *has* made its way into OpenBSD's CVS HEAD, but unless you're feeling very brave, you may not want to take that leap. You should, however, read this mailing list thread: http://marc.info/?t=136248759400010&r=1&w=4 Even after patching with the latest userspace and kernel code patches, I still needed to add the following to src/sys/miscfs/fuse/fuse_vfsops.c, as line 23: #include For me, it wouldn't build otherwise. You should be familiar with building a kernel, and your entire userspace; if not, read the OpenBSD FAQ. Once you've built your kernel, installed it, and built and installed your new userspace, reboot. You may also need to copy /usr/src/lib/libfuse/fuse.h to /usr/include/fuse.h. (I did.) Also, for the tests, I recommend installing devel/p5-Lchown, and installing Filesys::Statvfs, Unix::Mknod and Test::Pod out of CPAN. Okay, once you've done all that, run (as root): cd /dev ./MAKEDEV fuse If that doesn't work, do this (also as root): cd /usr/src/etc make cp etc.`uname -m`/MAKEDEV /dev cd /dev ./MAKEDEV fuse Now, in your perl-fuse distribution, run: perl Makefile.PL make You'll probably need to 'make test' as root. If you want to run your FUSE filesystem as non-root, run the following (as root): sysctl kern.usermount=1 chmod 0660 /dev/fuse0 Now, you should be able to run 'make test'. Yes, there are some test failures. No, those actually aren't our fault. Here are some things you should know about the state of FUSE on OpenBSD: * truncate() does not work. This is not our fault. The fuse kernel driver doesn't implement any support for truncate(), never mind libfuse. It literally doesn't do anything with it. * You can't make anything other than plain files, directories and symlinks right now. The kernel driver doesn't support it yet. * There is a bug if a file is created in the fuse filesystem and goes away, then you create another file of the same name via FUSE and try to do utime(). Not sure if it's just utime() or if other things trip it too, but I discovered that via playing around. I *THINK* it's a vnode caching problem. * There's a reason the rename() test doesn't run. IT WILL CRASH YOUR KERNEL. No joke. The rename() op in the kernel driver is busted. * The kernel code passes the trailing null character at the end of the read symlink path in readlink(). This causes all sorts of fun trouble. This needs to be fixed in the kernel driver. * fuse_get_context() returns an undef, because the reimplemented libfuse doesn't implement that. It also doesn't do any argument handling at all. Hence why it's all #ifndef'd out for OpenBSD. * mknod() will not get called to create a plain file. You need to implement create(), unless the OpenBSD devs fix libfuse to call mknod() for plain files. * You should probably implement release(); the kernel driver whines a lot about the ENOSYS if you don't. * You should probably implement all of chown(), chmod() and utime() and/or utimens(). The kernel driver will mask out future setattr() requests if it gets ENOSYS from ANY of these. Oops. * Oh, and don't interrupt the FUSE implementation while it's running. If you do (or if it aborts for some reason), umount the mountpoint ASAP. If you don't, and especially if you then try to mount the filesystem on that mountpoint again, you can hang your kernel. Not even kidding. I have done this. Anyway, happy FUSEing! Fuse-0.16.1/AUTHORS0000644000175000017500000000202211612426606012537 0ustar demondemonPerl bindings ------------- Mark Glines - original author of perl bindings, ithreads Dobrica Pavlinusic - current maintainer Richard Dawe - lot of improvements Mark Wilkinson - added mount options Csaba Henk - update to API 25 Vladimir V. Kolpakov - contributed cleanup for warnings Andrew Chadwick - fixes for Ubuntu and dh-make-perl (fakeroot) Chris Dolan - fixes for MacFuse 1.1.0 Reuben Thomas - contributed examples/filter_attr_fs.pl jaslong from CPAN::Forum - documentation patch for fuse_get_context Justin Fletcher - added file handles on open files Derrik Pates - added 64-bit support and fuse 2.6 binding, *BSD support Daniel Frett - make static callbacks thread-safe Alex Sudakov - fixes for readdir support Bojan Petrovic - FreeBSD support Fuse-0.16.1/META.yml0000644000175000017500000000144112235014036012734 0ustar demondemon--- #YAML:1.0 name: Fuse version: 0.16.1 abstract: write filesystems in Perl using FUSE author: - Mark Glines license: LGPL_2_1 distribution_type: module configure_requires: ExtUtils::MakeMaker: 0 build_requires: ExtUtils::MakeMaker: 0 requires: Filesys::Statvfs: 0 Lchown: 0 Unix::Mknod: 0 resources: bugtracker: https://rt.cpan.org/Public/Dist/Display.html?Name=Fuse license: http://www.gnu.org/licenses/lgpl-2.1.html repository: http://github.com/dpavlin/perl-fuse no_index: directory: - t - inc generated_by: ExtUtils::MakeMaker version 6.57_05 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 Fuse-0.16.1/Makefile.PL0000644000175000017500000001357612234560506013460 0ustar demondemonuse ExtUtils::MakeMaker; use POSIX; use Config; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. # Note: This is a hack. This hack is necessary because MacFUSE's libfuse # (and libfuse_ino64, by extension) don't link in libiconv. This wouldn't # be a problem, but it appears the Darwin/OS X dynamic linker won't # satisfy runtime link dependencies in those libraries from libraries # imported by our library, and it uses a symbol from libiconv without # actually linking the library to itself. Awesome. package MY; sub test_via_harness { my($self, $perl, $tests) = @_; local $_ = $self->SUPER::test_via_harness($perl, $tests); s/PERL_DL_NONLAZY=1//g if $^O eq 'darwin'; return $_; } sub test_via_script { my($self, $perl, $tests) = @_; local $_ = $self->SUPER::test_via_script($perl, $tests); s/PERL_DL_NONLAZY=1//g if $^O eq 'darwin'; return $_; } package main; chomp(my $fusever = `pkg-config --modversion fuse 2> /dev/null`); # Required for refuse on NetBSD if (!$fusever && $^O eq 'netbsd') { chomp($fusever = `fusermount -V`); $fusever =~ s/^.*?version:\s+//; } unless ($fusever) { # make CPANPLUS happy and don't report errors if fuse isn't installed my $explanation; if ($^O eq 'linux') { if (-e '/etc/debian_version') { $explanation = 'You need to install "libfuse-dev" to provide build support for this module'; } elsif (-e '/etc/redhat-release') { $explanation = 'You need to install "fuse-devel" to provide build support for this module'; } else { $explanation = 'I don\'t know your Linux distribution, but please install the FUSE libraries and headers to build this module'; } } elsif ($^O eq 'freebsd') { $explanation = 'You need to install the "fusefs-libs" package from ports to support this module'; } elsif ($^O eq 'netbsd') { $explanation = 'Could not find librefuse? Maybe install "perfuse" from pkgsrc, or upgrade to newer NetBSD'; } elsif ($^O eq 'darwin') { $explanation = 'Please install OSXFUSE from http://osxfuse.github.com/'; } elsif ($^O eq 'solaris') { open(my $motd, '<', '/etc/motd'); my $line = <$motd>; if ($line =~ /^OpenIndiana /) { $explanation = 'Please enable the \'sfe\' repository, and install the libfuse package'; } else { $explanation = 'Don\'t know how to enable FUSE support on this Solaris flavor'; } } else { $explanation = 'There is no FUSE support for your platform to our knowledge, sorry'; } die("Cannot build for platform: $^O\n", $explanation, "\n"); } if ($fusever && $fusever + 0 < 2.6) { die "FUSE API is ", $fusever, ", must be 2.6 or later\n"; } else { warn "fuse version found: ", $fusever, "\n"; } chomp(my $inc = `pkg-config --cflags-only-I fuse 2> /dev/null`); chomp(my $libs = `pkg-config --libs-only-L fuse 2> /dev/null`); chomp($libs .= `pkg-config --libs-only-l fuse 2> /dev/null` || (($^O eq 'netbsd') ? '-lrefuse' : '-lfuse')); # Needed for Fuse on OS X 10.6, due to 10.6 and up always using the 64-bit # inode structs; unfortunately MacFuse doesn't just do the right thing # on its own. (Not applicable for OSXFUSE; it uses a new SONAME, so we # don't have to worry about conflicts/compatibility, it "just works".) if ($^O eq 'darwin' && (uname())[2] =~ /^1[01]\./) { $libs =~ s/-lfuse\b/-lfuse_ino64/; } chomp(my $def = '-Wall -DFUSE_USE_VERSION=26 ' . `pkg-config --cflags-only-other fuse 2> /dev/null` || '-D_FILE_OFFSET_BITS=64'); chomp($def .= `pkg-config --libs-only-other fuse 2> /dev/null`); $def .= ' -DPERL_HAS_64BITINT' if $Config{'use64bitint'}; $def .= ' -DUSING_LIBREFUSE' if $libs =~ m{-lrefuse\b}; # As a feature was added in a micro version update (fallocate(), # specifically), we need to know the micro version level, and there's # nothing in the headers that supplies it, so I'm gonna ghetto this # up. my ($major, $minor, $micro) = split(m{\.}, $fusever); if (!defined $micro) { $micro = 0; } $def .= ' -DFUSE_FOUND_MAJOR_VER=' . $major; $def .= ' -DFUSE_FOUND_MINOR_VER=' . $minor; $def .= ' -DFUSE_FOUND_MICRO_VER=' . $micro; WriteMakefile( 'NAME' => 'Fuse', 'VERSION_FROM' => 'Fuse.pm', # finds $VERSION 'PREREQ_PM' => { # e.g., Module::Name => 1.1 'Lchown' => 0, 'Filesys::Statvfs' => 0, 'Unix::Mknod' => 0, }, ($] >= 5.005 ? ## Add these new keywords supported since 5.005 (ABSTRACT_FROM => 'Fuse.pm', # retrieve abstract from module AUTHOR => 'Mark Glines ') : ()), ($ExtUtils::MakeMaker::VERSION < 6.3002 ? () : ( 'LICENSE' => 'LGPL_2_1', )), ($ExtUtils::MakeMaker::VERSION < 6.46 ? () : ( META_MERGE => { resources => { license => 'http://www.gnu.org/licenses/lgpl-2.1.html', bugtracker => 'https://rt.cpan.org/Public/Dist/Display.html?Name=Fuse', repository => 'http://github.com/dpavlin/perl-fuse' }, }) ), 'LIBS' => $libs, # e.g., '-lm' 'DEFINE' => $def, # e.g., '-DHAVE_SOMETHING' 'OPTIMIZE' => '-g -ggdb', # Insert -I. if you add *.h files later: 'INC' => $inc, # e.g., '-I/usr/include/other' # Un-comment this if you add C files to link with later: 'OBJECT' => 'Fuse$(OBJ_EXT)', # link all the C files too ); sub MY::postamble { return <<'MAKE_MORE'; cpan: make clean rm -f Fuse-*.tar.gz perl Makefile.PL make dist make disttest @echo @echo -n "Upload" Fuse-*.tar.gz "to CPAN? [y/N]:" @read upload && test "$$upload" == "y" && cpan-upload -verbose Fuse-*.tar.gz sf: svn2cvs.pl file:///home/dpavlin/private/svn/fuse/perl-llin :ext:dpavlin@fuse.cvs.sourceforge.net:/cvsroot/fuse perl MAKE_MORE }; # vim: ts=4 ai et hls Fuse-0.16.1/examples/0000755000175000017500000000000012235014036013301 5ustar demondemonFuse-0.16.1/examples/fsel.pl0000755000175000017500000001263611723447270014615 0ustar demondemon#!/usr/bin/env perl use strict; no strict qw(refs); use threads; use threads::shared; use Carp; local $SIG{'__WARN__'} = \&Carp::cluck; use Fuse qw(:all); use Fcntl qw(:mode); use POSIX; use IO::Poll qw(POLLIN); use Time::HiRes qw(sleep); use Getopt::Long; # $fsel_open_mask is used to limit the number of opens to 1 per file. This # uses the file index (0-F) as $fh, as poll support requires a unique handle # per open file. Lifting this would require more complete open file # management. my $fsel_open_mask :shared = 0; # Maximum "file" size. use constant FSEL_CNT_MAX => 10; use constant FSEL_FILES => 16; # Used only as a lock for $fsel_poll_notify_mask and @fsel_cnt. my $fsel_mutex :shared; # Mask indicating what FDs have poll notifications waiting. my $fsel_poll_notify_mask :shared = 0; # Poll notification handles. my @fsel_poll_handle :shared; # Number of bytes for each "file". my @fsel_cnt :shared; # Initialize all byte counts. map { $fsel_cnt[$_] = 0 } (0 .. (FSEL_FILES - 1)); sub fsel_path_index { my ($path) = @_; print 'called ', (caller(0))[3], "\n"; return -1 if $path !~ m{^/([0-9A-F])$}; return hex($1); } sub fsel_getattr { my ($path) = @_; print 'called ', (caller(0))[3], "\n"; my @stbuf = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); if ($path eq '/') { @stbuf[2, 3] = (S_IFDIR | 0555, 2); return @stbuf; } my $idx = fsel_path_index($path); return -&ENOENT if $idx < 0; @stbuf[2, 3, 7] = (S_IFREG | 0444, 1, $fsel_cnt[$idx]); return @stbuf; } sub fsel_readdir { my ($path, $offset) = @_; print 'called ', (caller(0))[3], "\n"; return -&ENOENT if $path ne '/'; return('.', '..', map { sprintf('%X', $_) } (0 .. (FSEL_FILES - 1)), 0); } sub fsel_open { my ($path, $flags, $info) = @_; print 'called ', (caller(0))[3], "\n"; my $idx = fsel_path_index($path); return -&ENOENT if $idx < 0; return -&EACCES if $flags & O_ACCMODE != O_RDONLY; return -&EBUSY if $fsel_open_mask & (1 << $idx); $fsel_open_mask |= (1 << $idx); # fsel files are nonseekable somewhat pipe-like files which get filled # up periodically by the producer thread, and consumed on read. Tell # FUSE to do this right. @{$info}{'direct_io', 'nonseekable'} = (1, 1); return (0, $idx); } sub fsel_release { my ($path, $flags, $fh) = @_; print 'called ', (caller(0))[3], "\n"; $fsel_open_mask &= ~(1 << $fh); return 0; } sub fsel_read { my ($path, $size, $offset, $fh) = @_; print 'called ', (caller(0))[3], "\n"; lock($fsel_mutex); if ($fsel_cnt[$fh] < $size) { $size = $fsel_cnt[$fh]; } printf("READ \%X transferred=\%u cnt=\%u\n", $fh, $size, $fsel_cnt[$fh]); $fsel_cnt[$fh] -= $size; return(chr($fh) x $size); } our $polled_zero :shared = 0; sub fsel_poll { my ($path, $ph, $revents, $fh) = @_; print 'called ', (caller(0))[3], ", path = \"$path\", fh = $fh, revents = $revents\n"; lock($fsel_mutex); if ($ph) { my $oldph = $fsel_poll_handle[$fh]; pollhandle_destroy($oldph) if $oldph; $fsel_poll_notify_mask |= (1 << $fh); $fsel_poll_handle[$fh] = $ph; } if ($fsel_cnt[$fh]) { $revents |= POLLIN; printf("POLL \%X cnt=\%u polled_zero=\%u\n", $fh, $fsel_cnt[$fh], $polled_zero); $polled_zero = 0; } else { $polled_zero++; } return(0, $revents); } sub fsel_producer { print 'called ', (caller(0))[3], "\n"; local $SIG{'KILL'} = sub { threads->exit(); }; my ($tv, $idx, $nr) = (0.25, 0, 1); while (1) { { my ($i, $t); lock($fsel_mutex); # This is the main producer loop which is executed every 250 # msec. On each iteration, it adds one byte to 1, 2 or 4 files # and sends a poll notification if a poll handle is present. for (($i, $t) = (0, $idx); $i < $nr; $i++, $t = (($t + int(FSEL_FILES / $nr)) % FSEL_FILES)) { next if $fsel_cnt[$t] == FSEL_CNT_MAX; $fsel_cnt[$t]++; if ($fsel_poll_notify_mask & (1 << $t)) { printf("NOTIFY \%X\n", $t); my $ph = $fsel_poll_handle[$t]; notify_poll($ph); pollhandle_destroy($ph); $fsel_poll_notify_mask &= ~(1 << $t); $fsel_poll_handle[$t] = undef; } } $idx = ($idx + 1) % FSEL_FILES; if ($idx == 0) { # Cycle through 1, 2 and 4. $nr = ($nr * 2) % 7; } } sleep($tv); } } croak("Fuse doesn't have poll") unless Fuse::fuse_version() >= 2.8; my %fuseargs = ( 'getattr' => 'main::fsel_getattr', 'readdir' => 'main::fsel_readdir', 'open' => 'main::fsel_open', 'release' => 'main::fsel_release', 'read' => 'main::fsel_read', 'poll' => 'main::fsel_poll', ); GetOptions( 'use-threads' => sub { print STDERR "Warning: Fuse currently has bugs related to threading which may cause misbehavior\n"; $fuseargs{'threaded'} = 1; }, 'debug' => sub { $fuseargs{'debug'} = 1; } ) || croak("Malformed options passed"); $fuseargs{'mountpoint'} = $ARGV[0]; my $thread = threads->create(\&fsel_producer); Fuse::main(%fuseargs); $thread->kill('KILL'); $thread->join(); Fuse-0.16.1/examples/loopback.pl0000755000175000017500000002150712214632443015445 0ustar demondemon#!/usr/bin/perl -w use strict; use Carp (); local $SIG{'__WARN__'} = \&Carp::cluck; my $has_threads = 0; eval { require threads; require threads::shared; 1; } and do { $has_threads = 1; threads->import(); threads::shared->import(); }; my $has_Filesys__Statvfs = 0; eval { require Filesys::Statvfs; 1; } and do { $has_Filesys__Statvfs = 1; Filesys::Statvfs->import(); }; my $use_lchown = 0; eval { require Lchown; 1; } and do { $use_lchown = 1; Lchown->import(); }; my $has_mknod = 0; eval { require Unix::Mknod; 1; } and do { $has_mknod = 1; Unix::Mknod->import(); }; use blib; use Fuse; use IO::File; use POSIX qw(ENOTDIR ENOENT ENOSYS EEXIST EPERM O_RDONLY O_RDWR O_APPEND O_CREAT setsid); use Fcntl qw(S_ISBLK S_ISCHR S_ISFIFO SEEK_SET S_ISREG S_ISFIFO S_IMODE S_ISCHR S_ISBLK S_ISSOCK); use Getopt::Long; my %extraopts = ( 'threaded' => 0, 'debug' => 0 ); my($use_real_statfs, $pidfile, $logfile); GetOptions( 'use-threads' => sub { if ($has_threads) { $extraopts{'threaded'} = 1; } }, 'debug' => sub { $extraopts{'debug'} = 1; }, 'use-real-statfs' => \$use_real_statfs, 'pidfile=s' => \$pidfile, 'logfile=s' => \$logfile, ) || die('Error parsing options'); sub fixup { return "/tmp/fusetest-" . $ENV{LOGNAME} . shift } sub x_getattr { my ($file) = fixup(shift); my (@list) = lstat($file); return -$! unless @list; return @list; } sub x_getdir { my ($dirname) = fixup(shift); unless(opendir(DIRHANDLE,$dirname)) { return -ENOENT(); } my (@files) = readdir(DIRHANDLE); closedir(DIRHANDLE); return (@files, 0); } sub x_open { my ($file) = fixup(shift); my ($mode) = shift; return -$! unless sysopen(FILE,$file,$mode); close(FILE); return 0; } sub x_release { my ($file) = fixup(shift); return 0; } sub x_read { my ($file,$bufsize,$off) = @_; my ($rv) = -ENOSYS(); my ($handle) = new IO::File; return -ENOENT() unless -e ($file = fixup($file)); my ($fsize) = -s $file; return -ENOSYS() unless open($handle,$file); if(seek($handle,$off,SEEK_SET)) { read($handle,$rv,$bufsize); } return $rv; } sub x_read_buf { my ($file, $size, $off, $bufvec) = @_; my $rv = 0; my ($handle) = new IO::File; return -ENOENT() unless -e ($file = fixup($file)); my ($fsize) = -s $file; return -ENOSYS() unless open($handle,$file); if(seek($handle,$off,SEEK_SET)) { $rv = $bufvec->[0]{'size'} = read($handle,$bufvec->[0]{'mem'},$size); } return $rv; } sub x_write { my ($file,$buf,$off) = @_; my ($rv); return -ENOENT() unless -e ($file = fixup($file)); my ($fsize) = -s $file; return -ENOSYS() unless open(FILE,'+<',$file); if($rv = seek(FILE,$off,SEEK_SET)) { $rv = print(FILE $buf); } $rv = -ENOSYS() unless $rv; close(FILE); return length($buf); } sub x_write_buf { my ($file,$off,$bufvec) = @_; my ($rv); return -ENOENT() unless -e ($file = fixup($file)); my ($fsize) = -s $file; return -ENOSYS() unless open(FILE,'+<',$file); # If by some chance we get a non-contiguous buffer, or an FD-based # buffer (or both!), then copy all of it into one contiguous buffer. if ($#$bufvec > 0 || $bufvec->[0]{flags} & &Fuse::FUSE_BUF_IS_FD()) { my $single = [ { flags => 0, fd => -1, mem => undef, pos => 0, size => Fuse::fuse_buf_size($bufvec), } ]; Fuse::fuse_buf_copy($single, $bufvec); $bufvec = $single; } if($rv = seek(FILE,$off,SEEK_SET)) { $rv = print(FILE $bufvec->[0]{mem}); } $rv = -ENOSYS() unless $rv; close(FILE); return $rv; } sub err { return (-shift || -$!) } sub x_readlink { return readlink(fixup(shift)); } sub x_unlink { return unlink(fixup(shift)) ? 0 : -$!; } sub x_symlink { print "symlink\n"; return symlink(shift,fixup(shift)) ? 0 : -$!; } sub x_rename { my ($old) = fixup(shift); my ($new) = fixup(shift); my ($err) = rename($old,$new) ? 0 : -ENOENT(); return $err; } sub x_link { return link(fixup(shift),fixup(shift)) ? 0 : -$! } sub x_chown { my ($fn) = fixup(shift); local $!; print "nonexistent $fn\n" unless -e $fn; my ($uid,$gid) = @_; if( $use_lchown ){ lchown($uid, $gid, $fn); }else{ chown($uid, $gid, $fn); } return -$!; } sub x_chmod { my ($fn) = fixup(shift); my ($mode) = shift; my ($err) = chmod($mode,$fn) ? 0 : -$!; return $err; } sub x_truncate { return truncate(fixup(shift),shift) ? 0 : -$! ; } sub x_utime { return utime($_[1],$_[2],fixup($_[0])) ? 0:-$!; } sub x_mkdir { my ($name, $perm) = @_; return 0 if mkdir(fixup($name),$perm); return -$!; } sub x_rmdir { return 0 if rmdir fixup(shift); return -$!; } sub x_create { my ($file, $modes, $flags) = @_; printf(STDERR "x_create(): file: \"\%s\"; modes: \%o; flags: \%o\n", $file, $modes, $flags); $file = fixup($file); open(FILE, '>', $file) || return -$!; print FILE ''; close(FILE); chmod S_IMODE($modes), $file; return 0; } sub x_mknod { # since this is called for ALL files, not just devices, I'll do some checks # and possibly run the real mknod command. my ($file, $modes, $dev) = @_; $file = fixup($file); undef $!; if (S_ISREG($modes)) { open(FILE, '>', $file) || return -$!; print FILE ''; close(FILE); chmod S_IMODE($modes), $file; return 0; } elsif (S_ISFIFO($modes)) { my ($rv) = POSIX::mkfifo($file, S_IMODE($modes)); return $rv ? 0 : -POSIX::errno(); } elsif (S_ISCHR($modes) || S_ISBLK($modes)) { if($has_mknod){ Unix::Mknod::mknod($file, $modes, $dev); return -$!; }else{ return -POSIX::errno(); } } # S_ISSOCK maybe should be handled; however, for our test it should # not really matter. else { return -&ENOSYS; } return -$!; } # kludge sub x_statfs { if ($has_Filesys__Statvfs && $use_real_statfs) { (my($bsize, $frsize, $blocks, $bfree, $bavail, $files, $ffree, $favail, $flag, $namemax) = statvfs('/tmp')) || return -$!; return ($namemax, $files, $ffree, $blocks, $bavail, $bsize); } return 255,1000000,500000,1000000,500000,4096; } # Required for some edge cases where a simple fork() won't do. # from http://perldoc.perl.org/perlipc.html#Complete-Dissociation-of-Child -from-Parent sub daemonize { chdir("/") || die "can't chdir to /: $!"; open(STDIN, '<', '/dev/null') || die "can't read /dev/null: $!"; if ($logfile) { open(STDOUT, '>', $logfile) || die "can't open logfile: $!"; } else { open(STDOUT, '>', '/dev/null') || die "can't write to /dev/null: $!"; } defined(my $pid = fork()) || die "can't fork: $!"; exit if $pid; # non-zero now means I am the parent (setsid() != -1) || die "Can't start a new session: $!"; open(STDERR, '>&', \*STDOUT) || die "can't dup stdout: $!"; if ($pidfile) { open(PIDFILE, '>', $pidfile); print PIDFILE $$, "\n"; close(PIDFILE); } } my ($mountpoint) = ''; if (@ARGV){ $mountpoint = shift(@ARGV) } else { print <<'_EOT_'; Usage: loopback.pl [options] Options: --debug Turn on debugging (verbose) output --use-threads Use threads --use-real-statfs Use real stat command against /tmp or generic values --pidfile Create a file at the provided path containing PID --logfile Direct stdout/stderr to file instead of /dev/null _EOT_ exit; } if (! -d $mountpoint) { print STDERR "ERROR: attempted to mount to non-directory\n"; return -&ENOTDIR } daemonize(); Fuse::main( 'mountpoint' => $mountpoint, 'getattr' => 'main::x_getattr', 'readlink' => 'main::x_readlink', 'getdir' => 'main::x_getdir', 'create' => 'main::x_create', 'mknod' => 'main::x_mknod', 'mkdir' => 'main::x_mkdir', 'unlink' => 'main::x_unlink', 'rmdir' => 'main::x_rmdir', 'symlink' => 'main::x_symlink', 'rename' => 'main::x_rename', 'link' => 'main::x_link', 'chmod' => 'main::x_chmod', 'chown' => 'main::x_chown', 'truncate' => 'main::x_truncate', 'utime' => 'main::x_utime', 'open' => 'main::x_open', 'release' => 'main::x_release', 'read' => 'main::x_read', 'read_buf' => 'main::x_read_buf', 'write' => 'main::x_write', 'write_buf' => 'main::x_write_buf', 'statfs' => 'main::x_statfs', %extraopts, ); # vim: ts=4 ai et hls Fuse-0.16.1/examples/fioc.pl0000755000175000017500000001067412061272017014573 0ustar demondemon#!/usr/bin/env perl # fioc.pl: A Perl conversion of the fioc example IOCTL server program # from the FUSE distribution. I've endeavored to stay pretty close # structure-wise to the C version, while using Perl-specific features. # I wrote this to provide a way to verify my ioctl() wrapper # implementation would work properly. So far, it seems to, and it will # interoperate with the C client as well. use strict; no strict qw(refs); use threads; use threads::shared; use Carp; local $SIG{'__WARN__'} = \&Carp::cluck; use Fuse qw(:all); use Fcntl qw(:mode); use POSIX; my $fioc_size :shared = 0; use constant FIOC_NAME => 'fioc'; my $fioc_buf :shared = ''; use constant FIOC_NONE => 0; use constant FIOC_ROOT => 1; use constant FIOC_FILE => 2; if ($^O eq 'linux') { require 'linux/ioctl.ph'; } else { require 'sys/ioccom.ph'; } our %sizeof = ('size_t' => length(pack('L!'))); sub FIOC_GET_SIZE { _IOR(ord 'E', 0, 'size_t'); } sub FIOC_SET_SIZE { _IOW(ord 'E', 1, 'size_t'); } sub TCGETS { 0x5401; } sub fioc_resize { my ($size) = @_; print 'called ', (caller(0))[3], "\n"; return 0 if $size == $fioc_size; if ($size < $fioc_size) { $fioc_buf = substr($fioc_buf, 0, $size); } else { $fioc_buf .= "\0" x ($size - $fioc_size); } $fioc_size = $size; return 0; } sub fioc_expand { my ($size) = @_; print 'called ', (caller(0))[3], "\n"; if ($size > $fioc_size) { return fioc_resize($size); } return 0; } sub fioc_file_type { my ($path) = @_; print 'called ', (caller(0))[3], "\n"; return FIOC_ROOT if $path eq '/'; return FIOC_FILE if $path eq '/' . FIOC_NAME; return FIOC_NONE; } sub fioc_getattr { my ($path) = @_; print 'called ', (caller(0))[3], "\n"; my @stbuf = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); $stbuf[4] = $<; $stbuf[5] = (split(/\s+/, $())[0]; $stbuf[8] = $stbuf[9] = time(); my $type = fioc_file_type($path); if ($type == FIOC_ROOT) { $stbuf[2] = S_IFDIR | 0755; $stbuf[3] = 2; } elsif ($type == FIOC_FILE) { $stbuf[2] = S_IFREG | 0644; $stbuf[3] = 1; $stbuf[7] = $fioc_size; } else { return -&ENOENT; } return @stbuf; } sub fioc_open { my ($path, $flags, $info) = @_; print 'called ', (caller(0))[3], "\n"; return 0 if fioc_file_type($path) != FIOC_NONE; return -&ENOENT; } sub fioc_read { my ($path, $size, $offset) = @_; print 'called ', (caller(0))[3], "\n"; return -&EINVAL if fioc_file_type($path) != FIOC_FILE; return q{} if $offset > $fioc_size; if ($size > $fioc_size - $offset) { $size - $fioc_size - $offset; } return substr($fioc_buf, $offset, $size); } sub fioc_write { my ($path, $data, $offset) = @_; print 'called ', (caller(0))[3], "\n"; lock($fioc_buf); return -&EINVAL if fioc_file_type($path) != FIOC_FILE; return -&ENOMEM if fioc_expand($offset + length($data)); substr($fioc_buf, $offset, length($data), $data); return length($data); } sub fioc_truncate { my ($path, $size) = @_; print 'called ', (caller(0))[3], "\n"; lock($fioc_buf); return -&EINVAL if fioc_file_type($path) != FIOC_FILE; return fioc_resize($size); } sub fioc_readdir { my ($path, $offset) = @_; print 'called ', (caller(0))[3], "\n"; return -&EINVAL if fioc_file_type($path) != FIOC_ROOT; return ('.', '..', FIOC_NAME, 0); } sub fioc_ioctl { my ($path, $cmd, $flags, $data) = @_; print 'called ', (caller(0))[3], "\n"; return -&EINVAL if fioc_file_type($path) != FIOC_FILE; return -&ENOSYS if $flags & FUSE_IOCTL_COMPAT; if ($cmd == FIOC_GET_SIZE) { return(0, pack('L!', $fioc_size)); } elsif ($cmd == FIOC_SET_SIZE) { lock($fioc_buf); fioc_resize(unpack('L!', $data)); return 0; } elsif ($cmd == TCGETS) { # perl sends TCGETS as part of calling isatty() on opening a file; # this appears to be a more canonical answer return -&ENOTTY; } return -&EINVAL; } croak("Fuse doesn't have ioctl") unless Fuse::fuse_version() >= 2.8; Fuse::main( 'mountpoint' => $ARGV[0], 'getattr' => 'main::fioc_getattr', 'readdir' => 'main::fioc_readdir', 'truncate' => 'main::fioc_truncate', 'open' => 'main::fioc_open', 'read' => 'main::fioc_read', 'write' => 'main::fioc_write', 'ioctl' => 'main::fioc_ioctl', 'threaded' => 1); Fuse-0.16.1/examples/fselclient.pl0000755000175000017500000000140211723447270016001 0ustar demondemon#!/usr/bin/env perl use strict; no strict qw(refs); use Carp; local $SIG{'__WARN__'} = \&Carp::cluck; use IO::Poll qw(POLLIN); use Fcntl; use constant FSEL_FILES => 16; my @fds; foreach my $i (0 .. (FSEL_FILES - 1)) { sysopen($fds[$i], $ARGV[0] . '/' . sprintf('%X', $i), O_RDONLY) or croak($!); } my $poll = new IO::Poll; foreach my $fd (@fds) { $poll->mask($fd, POLLIN); } while (1) { my $rc = $poll->poll(); croak($!) if $rc < 0; foreach my $i (0 .. (FSEL_FILES - 1)) { if (!$poll->events($fds[$i])) { print '_: '; next; } printf('%X:', $i); $rc = sysread($fds[$i], my $buf, 4096); croak($!) if !defined($rc); printf('%02d ', $rc); } print "\n"; } Fuse-0.16.1/examples/rmount.pl0000755000175000017500000000355612061272017015200 0ustar demondemon#!/usr/bin/perl -w # This example needs some work before it can support threads. use strict; use Net::SSH 'sshopen2'; use IPC::Open2; use Fuse; use Data::Dumper; my $port; if($ARGV[-1]=~/^--/){ $port = pop(@ARGV); $port =~ s/--port=//; } my ($host, $dir, $mount) = @ARGV; if(!defined($mount)) { $mount = $dir; if($host =~ /^(.*):(.*)$/) { ($host,$dir) = ($1,$2); } else { die "usage: $0 user\@host remotedir mountpoint [--port=]\n". "or : $0 user\@host:remotedir mountpoint [--port=]\n"; } } `umount $mount` unless -d $mount; die "mountpoint $mount isn't a directory!\n" unless -d $mount; my (%args) = (mountpoint => $mount); map { my ($str) = $_; $args{$str} = sub { netlink($str,@_) } } qw(getattr getdir open read write readlink unlink rmdir symlink rename link chown chmod truncate utime mkdir rmdir mknod statfs); sub connect_remote { push(@Net::SSH::ssh_options, "-p $port") if $port; sshopen2($host, *READER, *WRITER, "./rmount_remote.pl $dir") or die "ssh: $!\n"; select WRITER; $| = 1; select STDOUT; } $SIG{CHLD} = sub { use POSIX ":sys_wait_h"; my $kid; do { $kid = waitpid(-1,WNOHANG); } until $kid < 1; }; connect_remote; sub netlink { my ($str) = Dumper(\@_)."\n"; $str = sprintf("%08i\n%s",length($str),$str); while(1) { # retry as necessary my ($VAR1); $VAR1 = undef; eval { local $SIG{ALRM} = sub { die "timeout\n" }; alarm 10; print WRITER $str; my ($len, $data); $data = ''; if(sysread(READER,$len,9) == 9) { sysread(READER,$data,$len-length($data),length($data)) while(length($data) < $len); eval $data; } alarm 0; }; if(defined $VAR1) { return wantarray ? @{$VAR1} : $$VAR1[0]; } print STDERR "failed to send command; reconnecting ssh\n"; close(READER); close(WRITER); connect_remote(); } } Fuse::main(%args); netlink("bye"); close(READER); close(WRITER); Fuse-0.16.1/examples/rmount_remote.pl0000755000175000017500000000711311566236206016555 0ustar demondemon#!/usr/bin/perl use strict; use IO::File; use POSIX qw(ENOENT ENOSYS EEXIST EPERM O_RDONLY O_RDWR O_APPEND O_CREAT); use Fcntl qw(S_ISBLK S_ISCHR S_ISFIFO SEEK_SET); use Data::Dumper; require 'syscall.ph'; # for SYS_mknod and SYS_lchown my ($rootdir) = @ARGV; # strip leading and trailing slashes $rootdir = $1 if($rootdir =~ /^\/?(.*)\/?$/); sub fixup { return "/$rootdir" . shift } sub x_getattr { my ($file) = fixup(shift); my (@list) = lstat($file); return -$! unless @list; return @list; } sub x_getdir { my ($dirname) = fixup(shift); unless(opendir(DIRHANDLE,$dirname)) { return -ENOENT(); } my (@files) = readdir(DIRHANDLE); closedir(DIRHANDLE); return (@files, 0); } sub x_open { my ($file) = fixup(shift); my ($mode) = shift; return -$! unless sysopen(FILE,$file,$mode); close(FILE); return 0; } sub x_read { my ($file,$bufsize,$off) = @_; my ($rv) = -ENOSYS(); my ($handle) = new IO::File; return -ENOENT() unless -e ($file = fixup($file)); my ($fsize) = -s $file; return -ENOSYS() unless open($handle,$file); if(seek($handle,$off,SEEK_SET)) { read($handle,$rv,$bufsize); } return $rv; } sub x_write { my ($file,$buf,$off) = @_; my ($rv); return -ENOENT() unless -e ($file = fixup($file)); my ($fsize) = -s $file; return -ENOSYS() unless open(FILE,'+<',$file); if($rv = seek(FILE,$off,SEEK_SET)) { $rv = print(FILE $buf); } $rv = -ENOSYS() unless $rv; close(FILE); return length($buf); } sub err { return (-shift || -$!) } sub x_readlink { return readlink(fixup(shift) ); } sub x_unlink { return unlink(fixup(shift)) ? 0 : -$!; } sub x_rmdir { return err(rmdir(fixup(shift)) ); } sub x_symlink { print "symlink\n"; return symlink(shift,fixup(shift)) ? 0 : -$!; } sub x_rename { my ($old) = fixup(shift); my ($new) = fixup(shift); my ($err) = rename($old,$new) ? 0 : -ENOENT(); return $err; } sub x_link { return link(fixup(shift),fixup(shift)) ? 0 : -$! } sub x_chown { my ($fn) = fixup(shift); print "nonexistent $fn\n" unless -e $fn; my ($uid,$gid) = @_; # perl's chown() does not chown symlinks, it chowns the symlink's # target. it fails when the link's target doesn't exist, because # the stat64() syscall fails. # this causes error messages when unpacking symlinks in tarballs. my ($err) = syscall(&SYS_lchown,$fn,$uid,$gid,$fn) ? -$! : 0; return $err; } sub x_chmod { my ($fn) = fixup(shift); my ($mode) = shift; my ($err) = chmod($mode,$fn) ? 0 : -$!; return $err; } sub x_truncate { return truncate(fixup(shift),shift) ? 0 : -$! ; } sub x_utime { return utime($_[1],$_[2],fixup($_[0])) ? 0:-$!; } sub x_mkdir { my ($name, $perm) = @_; return 0 if mkdir(fixup($name),$perm); return -$!; } sub x_rmdir { return 0 if rmdir fixup(shift); return -$!; } sub x_mknod { # since this is called for ALL files, not just devices, I'll do some checks # and possibly run the real mknod command. my ($file, $modes, $dev) = @_; $file = fixup($file); $! = 0; syscall(&SYS_mknod,$file,$modes,$dev); return -$!; } # kludge sub x_statfs {return 255,1000000,500000,1000000,500000,4096} $| = 1; my ($len); while(read(STDIN,$len,9) == 9) { chomp $len; my ($data,$VAR1,@args); eval { $SIG{ALRM} = sub { die "timeout\n"}; $data = ""; alarm 5; read(STDIN,$data,$len-length($data),length($data)) while(length($data) < $len); alarm 0; }; die $@ if $@; eval $data; @args = @{$VAR1}; my $cmd = shift(@args); exit 0 if $cmd eq "bye"; die "cannot find command $cmd\n" unless exists($main::{"x_$cmd"}); @args = $main::{"x_$cmd"}(@args); $cmd = Dumper(\@args)."\n"; $cmd = sprintf("%08i\n%s",length($cmd),$cmd); print $cmd; } Fuse-0.16.1/examples/example.pl0000755000175000017500000000603311566236206015311 0ustar demondemon#!/usr/bin/perl -w use strict; use Data::Dumper; #use blib; use Fuse qw(fuse_get_context); use POSIX qw(ENOENT EISDIR EINVAL); my (%files) = ( '.' => { type => 0040, mode => 0755, ctime => time()-1000 }, a => { cont => "File 'a'.\n", type => 0100, mode => 0755, ctime => time()-2000 }, b => { cont => "This is file 'b'.\n", type => 0100, mode => 0644, ctime => time()-1000 }, me => { size => 45, type => 0100, mode => 0644, ctime => time()-1000 }, ); sub filename_fixup { my ($file) = shift; $file =~ s,^/,,; $file = '.' unless length($file); return $file; } sub e_getattr { my ($file) = filename_fixup(shift); $file =~ s,^/,,; $file = '.' unless length($file); return -ENOENT() unless exists($files{$file}); my ($size) = exists($files{$file}{cont}) ? length($files{$file}{cont}) : 0; $size = $files{$file}{size} if exists $files{$file}{size}; my ($modes) = ($files{$file}{type}<<9) + $files{$file}{mode}; my ($dev, $ino, $rdev, $blocks, $gid, $uid, $nlink, $blksize) = (0,0,0,1,0,0,1,1024); my ($atime, $ctime, $mtime); $atime = $ctime = $mtime = $files{$file}{ctime}; # 2 possible types of return values: #return -ENOENT(); # or any other error you care to #print(join(",",($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)),"\n"); return ($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks); } sub e_getdir { # return as many text filenames as you like, followed by the retval. print((scalar keys %files)."\n"); return (keys %files),0; } sub e_open { # VFS sanity check; it keeps all the necessary state, not much to do here. my $file = filename_fixup(shift); my ($flags, $fileinfo) = @_; print("open called $file, $flags, $fileinfo\n"); return -ENOENT() unless exists($files{$file}); return -EISDIR() if $files{$file}{type} & 0040; my $fh = [ rand() ]; print("open ok (handle $fh)\n"); return (0, $fh); } sub e_read { # return an error numeric, or binary/text string. (note: 0 means EOF, "0" will # give a byte (ascii "0") to the reading program) my ($file) = filename_fixup(shift); my ($buf, $off, $fh) = @_; print "read from $file, $buf \@ $off\n"; print "file handle:\n", Dumper($fh); return -ENOENT() unless exists($files{$file}); if(!exists($files{$file}{cont})) { return -EINVAL() if $off > 0; my $context = fuse_get_context(); return sprintf("pid=0x%08x uid=0x%08x gid=0x%08x\n",@$context{'pid','uid','gid'}); } return -EINVAL() if $off > length($files{$file}{cont}); return 0 if $off == length($files{$file}{cont}); return substr($files{$file}{cont},$off,$buf); } sub e_statfs { return 255, 1, 1, 1, 1, 2 } # If you run the script directly, it will run fusermount, which will in turn # re-run this script. Hence the funky semantics. my ($mountpoint) = ""; $mountpoint = shift(@ARGV) if @ARGV; Fuse::main( mountpoint=>$mountpoint, getattr=>"main::e_getattr", getdir =>"main::e_getdir", open =>"main::e_open", statfs =>"main::e_statfs", read =>"main::e_read", threaded=>0 ); Fuse-0.16.1/examples/fioclient.pl0000755000175000017500000000327112061272017015622 0ustar demondemon#!/usr/bin/env perl # fioclient.pl: A Perl version of the fioclient IOCTL client example from # the FUSE distribution. use strict; no strict qw(refs); use Carp; local $SIG{'__WARN__'} = \&Carp::cluck; use Fcntl qw(:mode); use Errno qw(:POSIX); use POSIX; if ($^O eq 'linux') { require 'linux/ioctl.ph'; } else { require 'sys/ioccom.ph'; } our %sizeof = ('size_t' => length(pack('L!'))); sub FIOC_GET_SIZE { _IOR(ord 'E', 0, 'size_t'); } sub FIOC_SET_SIZE { _IOW(ord 'E', 1, 'size_t'); } sub usage { print <<'_EOT_'; Usage: fioclient.pl FIOC_FILE COMMAND COMMANDS s [SIZE] : get size if SIZE is omitted, set size otherwise r SIZE [OFF] : read SIZE bytes @ OFF (default 0) and output to stdout w SIZE [OFF] : write SIZE bytes @ OFF (default 0) from stdin _EOT_ exit(1); } usage() if scalar(@ARGV) < 2; open(my $file, '+<', $ARGV[0]) or usage(); if ($ARGV[1] eq 's') { if (!defined $ARGV[2]) { my $size; my $rv = ioctl($file, FIOC_GET_SIZE, $size); if (!defined($rv) || $rv != 0) { croak($!); } printf("\%u\n", unpack('L!', $size)); } else { my $rv = ioctl($file, FIOC_SET_SIZE, pack('L!', $ARGV[2])); if (!defined($rv) || $rv != 0) { croak($!); } } } elsif ($ARGV[1] eq 'r' || $ARGV[1] eq 'w') { usage() unless defined $ARGV[2]; my $size = $ARGV[2]; my $off = 0; if (defined $ARGV[3]) { $off = $ARGV[3]; } seek($file, SEEK_SET, $off); if ($ARGV[1] eq 'r') { read($file, my $data, $size); print $data; } else { read(STDIN, my $data, $size); print $file $data; } } else { usage(); } close($file); Fuse-0.16.1/examples/example_t.pl0000755000175000017500000000474711566236206015646 0ustar demondemon#!/usr/bin/perl -w use strict; use threads; use threads::shared; use Fuse; use POSIX qw(ENOENT EISDIR EINVAL); my (%files) = ( '.' => { type => 0040, mode => 0755, ctime => time()-1000 }, a => { cont => "File 'a'.\n", type => 0100, mode => 0755, ctime => time()-2000 }, b => { cont => "This is file 'b'.\n", type => 0100, mode => 0644, ctime => time()-1000 }, ); sub filename_fixup { my ($file) = shift; $file =~ s,^/,,; $file = '.' unless length($file); return $file; } sub e_getattr { my ($file) = filename_fixup(shift); $file =~ s,^/,,; $file = '.' unless length($file); return -ENOENT() unless exists($files{$file}); my ($size) = exists($files{$file}{cont}) ? length($files{$file}{cont}) : 0; my ($modes) = ($files{$file}{type}<<9) + $files{$file}{mode}; my ($dev, $ino, $rdev, $blocks, $gid, $uid, $nlink, $blksize) = (0,0,0,1,0,0,1,1024); my ($atime, $ctime, $mtime); $atime = $ctime = $mtime = $files{$file}{ctime}; # 2 possible types of return values: #return -ENOENT(); # or any other error you care to #print(join(",",($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)),"\n"); return ($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks); } sub e_getdir { # return as many text filenames as you like, followed by the retval. print((scalar keys %files)."\n"); return (keys %files),0; } sub e_open { # VFS sanity check; it keeps all the necessary state, not much to do here. my ($file) = filename_fixup(shift); print("open called\n"); return -ENOENT() unless exists($files{$file}); return -EISDIR() unless exists($files{$file}{cont}); print("open ok\n"); return 0; } sub e_read { # return an error numeric, or binary/text string. (note: 0 means EOF, "0" will # give a byte (ascii "0") to the reading program) my ($file) = filename_fixup(shift); my ($buf,$off) = @_; return -ENOENT() unless exists($files{$file}); return -EINVAL() if $off > length($files{$file}{cont}); return 0 if $off == length($files{$file}{cont}); return substr($files{$file}{cont},$off,$buf); } sub e_statfs { return 255, 1, 1, 1, 1, 2 } # If you run the script directly, it will run fusermount, which will in turn # re-run this script. Hence the funky semantics. my ($mountpoint) = ""; $mountpoint = shift(@ARGV) if @ARGV; Fuse::main( mountpoint=>$mountpoint, getattr=>"main::e_getattr", getdir =>"main::e_getdir", open =>"main::e_open", statfs =>"main::e_statfs", read =>"main::e_read", threaded=>1 ); Fuse-0.16.1/Fuse.xs0000755000175000017500000015421612235012715012757 0ustar demondemon#define PERL_NO_GET_CONTEXT #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include #if defined(__linux__) || defined(__APPLE__) # include #else # define XATTR_CREATE 1 # define XATTR_REPLACE 2 #endif #ifdef __OpenBSD__ #define FUSE_MAJOR_VERSION FUSE_FOUND_MAJOR_VER #define FUSE_MINOR_VERSION FUSE_FOUND_MINOR_VER #endif /* defined(__OpenBSD__) */ #if defined(__linux__) || defined(__sun__) # define STAT_SEC(st, st_xtim) ((st)->st_xtim.tv_sec) # define STAT_NSEC(st, st_xtim) ((st)->st_xtim.tv_nsec) #else # define STAT_SEC(st, st_xtim) ((st)->st_xtim##espec.tv_sec) # define STAT_NSEC(st, st_xtim) ((st)->st_xtim##espec.tv_nsec) #endif /* Implement a macro to handle multiple formats (integer, float, and array * containing seconds and nanoseconds). */ #define PULL_TIME(st, st_xtim, svp) \ { \ SV *sv = svp; \ if (SvROK(sv)) { \ AV *av = (AV *)SvRV(sv); \ if (SvTYPE((SV *)av) != SVt_PVAV) { \ Perl_croak_nocontext("Reference was not array ref"); \ } \ if (av_len(av) != 1) { \ Perl_croak_nocontext("Array of incorrect dimension"); \ } \ STAT_SEC(st, st_xtim) = SvIV(*(av_fetch(av, 0, FALSE))); \ STAT_NSEC(st, st_xtim) = SvIV(*(av_fetch(av, 1, FALSE))); \ } \ else if (SvNOK(sv) || SvIOK(sv) || SvPOK(sv)) { \ double tm = SvNV(sv); \ STAT_SEC(st, st_xtim) = (int)tm; \ STAT_NSEC(st, st_xtim) = (tm - (int)tm) * 1000000000; \ } \ else { \ Perl_croak_nocontext("Invalid data type passed"); \ } \ } /* Determine if threads support should be included */ #ifdef USE_ITHREADS # ifdef I_PTHREAD # define FUSE_USE_ITHREADS # if (PERL_VERSION < 8) || (PERL_VERSION == 8 && PERL_SUBVERSION < 9) # define tTHX PerlInterpreter* # define STR_WITH_LEN(s) ("" s ""), (sizeof(s)-1) # define hv_fetchs(hv,key,lval) Perl_hv_fetch(aTHX_ hv, STR_WITH_LEN(key), lval) # define dMY_CXT_INTERP(interp) \ SV *my_cxt_sv = *hv_fetchs(interp->Imodglobal, MY_CXT_KEY, TRUE); \ my_cxt_t *my_cxtp = INT2PTR(my_cxt_t*, SvUV(my_cxt_sv)) # endif # else # warning "Sorry, I don't know how to handle ithreads on this architecture. Building non-threaded version" # endif #endif /* Global Data */ #define MY_CXT_KEY "Fuse::_guts" XS_VERSION #if FUSE_VERSION >= 29 # if FUSE_FOUND_MICRO_VER >= 1 # define N_CALLBACKS 45 # else /* FUSE_FOUND_MICRO_VER < 1 */ # define N_CALLBACKS 44 # endif #elif FUSE_VERSION >= 28 # define N_CALLBACKS 41 #else /* FUSE_VERSION < 28 */ # define N_CALLBACKS 38 #endif #define N_FLAGS 8 typedef struct { SV *callback[N_CALLBACKS]; HV *handles; #ifdef USE_ITHREADS tTHX self; #endif int threaded; #ifdef USE_ITHREADS perl_mutex mutex; #endif int utimens_as_array; } my_cxt_t; START_MY_CXT; #ifdef FUSE_USE_ITHREADS tTHX master_interp = NULL; #define CLONE_INTERP(parent) S_clone_interp(parent) tTHX S_clone_interp(tTHX parent) { # if (PERL_VERSION < 10) tTHX my_perl = parent; #endif dMY_CXT_INTERP(parent); if(MY_CXT.threaded) { MUTEX_LOCK(&MY_CXT.mutex); PERL_SET_CONTEXT(parent); dTHX; #if (PERL_VERSION > 10) || (PERL_VERSION == 10 && PERL_SUBVERSION >= 1) tTHX child = perl_clone(parent, CLONEf_CLONE_HOST | CLONEf_COPY_STACKS); #else tTHX child = perl_clone(parent, CLONEf_CLONE_HOST | CLONEf_COPY_STACKS | CLONEf_KEEP_PTR_TABLE); ptr_table_free(PL_ptr_table); PL_ptr_table = NULL; #endif MUTEX_UNLOCK(&MY_CXT.mutex); return child; } return NULL; } # define FUSE_CONTEXT_PRE dTHX; if(!aTHX) aTHX = CLONE_INTERP(master_interp); { dMY_CXT; dSP; # define FUSE_CONTEXT_POST } #else # define FUSE_CONTEXT_PRE dTHX; dMY_CXT; dSP; # define FUSE_CONTEXT_POST #endif #undef DEBUGf #if 0 #define DEBUGf(f, a...) fprintf(stderr, "%s:%d (%i): " f,__BASE_FILE__,__LINE__,sp-PL_stack_base ,##a ) #else #define DEBUGf(a...) #endif #define FH_KEY(fi) sv_2mortal(newSViv((fi)->fh)) #define FH_GETHANDLE(fi) S_fh_get_handle(aTHX_ aMY_CXT_ fi) #define FH_STOREHANDLE(fi,sv) S_fh_store_handle(aTHX_ aMY_CXT_ fi, sv) #define FH_RELEASEHANDLE(fi) S_fh_release_handle(aTHX_ aMY_CXT_ fi) SV *S_fh_get_handle(pTHX_ pMY_CXT_ struct fuse_file_info *fi) { SV *val; val = &PL_sv_undef; if(fi->fh != 0) { HE *he; if((he = hv_fetch_ent(MY_CXT.handles, FH_KEY(fi), 0, 0))) { val = HeVAL(he); SvGETMAGIC(val); } } return val; } void S_fh_release_handle(pTHX_ pMY_CXT_ struct fuse_file_info *fi) { if(fi->fh != 0) { (void)hv_delete_ent(MY_CXT.handles, FH_KEY(fi), G_DISCARD, 0); fi->fh = 0; } } void S_fh_store_handle(pTHX_ pMY_CXT_ struct fuse_file_info *fi, SV *sv) { if(SvOK(sv)) { #ifdef FUSE_USE_ITHREADS if(MY_CXT.threaded) { SvSHARE(sv); } #endif /* This seems to be screwing things up... */ // MAGIC *mg = (SvTYPE(sv) == SVt_PVMG) ? mg_find(sv, PERL_MAGIC_shared_scalar) : NULL; // fi->fh = mg ? PTR2IV(mg->mg_ptr) : PTR2IV(sv); fi->fh = PTR2IV(sv); if(hv_store_ent(MY_CXT.handles, FH_KEY(fi), SvREFCNT_inc(sv), 0) == NULL) { SvREFCNT_dec(sv); } SvSETMAGIC(sv); } } int _PLfuse_getattr(const char *file, struct stat *result) { int rv; FUSE_CONTEXT_PRE; DEBUGf("getattr begin: %s\n",file); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,strlen(file)))); PUTBACK; rv = call_sv(MY_CXT.callback[0],G_ARRAY); SPAGAIN; if(rv != 13) { if(rv > 1) { fprintf(stderr,"inappropriate number of returned values from getattr\n"); rv = -ENOSYS; } else if(rv) rv = POPi; else rv = -ENOENT; } else { result->st_blocks = POPi; result->st_blksize = POPi; PULL_TIME(result, st_ctim, POPs); PULL_TIME(result, st_mtim, POPs); PULL_TIME(result, st_atim, POPs); result->st_size = POPn; // we pop double here to support files larger than 4Gb (long limit) result->st_rdev = POPi; result->st_gid = POPi; result->st_uid = POPi; result->st_nlink = POPi; result->st_mode = POPi; result->st_ino = POPi; result->st_dev = POPi; rv = 0; } FREETMPS; LEAVE; PUTBACK; DEBUGf("getattr end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_readlink(const char *file,char *buf,size_t buflen) { int rv; if(buflen < 1) return EINVAL; FUSE_CONTEXT_PRE; DEBUGf("readlink begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); PUTBACK; rv = call_sv(MY_CXT.callback[1],G_SCALAR); SPAGAIN; if(!rv) rv = -ENOENT; else { SV *mysv = POPs; if(SvTYPE(mysv) == SVt_IV || SvTYPE(mysv) == SVt_NV) rv = SvIV(mysv); else { strncpy(buf,SvPV_nolen(mysv),buflen); rv = 0; } } FREETMPS; LEAVE; buf[buflen-1] = 0; PUTBACK; DEBUGf("readlink end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_getdir(const char *file, fuse_dirh_t dirh, fuse_dirfil_t dirfil) { int prv, rv; SV **swp; FUSE_CONTEXT_PRE; DEBUGf("getdir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); PUTBACK; prv = call_sv(MY_CXT.callback[2],G_ARRAY); SPAGAIN; if(prv) { /* Should yield the bottom of the current stack... */ swp = SP - prv + 1; rv = POPi; /* Sort of a hack to walk the stack in order, instead of reverse * order - trying to explain to potential users why they need to * reverse the order of this array would be confusing, at best. */ while (swp <= SP) dirfil(dirh,SvPVx_nolen(*(swp++)),0,0); SP -= prv - 1; } else { fprintf(stderr,"getdir() handler returned nothing!\n"); rv = -ENOSYS; } FREETMPS; LEAVE; PUTBACK; DEBUGf("getdir end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_mknod (const char *file, mode_t mode, dev_t dev) { int rv; FUSE_CONTEXT_PRE; DEBUGf("mknod begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(mode))); XPUSHs(sv_2mortal(newSViv(dev))); PUTBACK; rv = call_sv(MY_CXT.callback[3],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("mknod end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_mkdir (const char *file, mode_t mode) { int rv; FUSE_CONTEXT_PRE; DEBUGf("mkdir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(mode))); PUTBACK; rv = call_sv(MY_CXT.callback[4],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("mkdir end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_unlink (const char *file) { int rv; FUSE_CONTEXT_PRE; DEBUGf("unlink begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); PUTBACK; rv = call_sv(MY_CXT.callback[5],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("unlink end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_rmdir (const char *file) { int rv; FUSE_CONTEXT_PRE; DEBUGf("rmdir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); PUTBACK; rv = call_sv(MY_CXT.callback[6],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("rmdir end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_symlink (const char *file, const char *new) { int rv; FUSE_CONTEXT_PRE; DEBUGf("symlink begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSVpv(new,0))); PUTBACK; rv = call_sv(MY_CXT.callback[7],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("symlink end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_rename (const char *file, const char *new) { int rv; FUSE_CONTEXT_PRE; DEBUGf("rename begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSVpv(new,0))); PUTBACK; rv = call_sv(MY_CXT.callback[8],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("rename end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_link (const char *file, const char *new) { int rv; FUSE_CONTEXT_PRE; DEBUGf("link begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSVpv(new,0))); PUTBACK; rv = call_sv(MY_CXT.callback[9],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("link end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_chmod (const char *file, mode_t mode) { int rv; FUSE_CONTEXT_PRE; DEBUGf("chmod begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(mode))); PUTBACK; rv = call_sv(MY_CXT.callback[10],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("chmod end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_chown (const char *file, uid_t uid, gid_t gid) { int rv; FUSE_CONTEXT_PRE; DEBUGf("chown begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(uid))); XPUSHs(sv_2mortal(newSViv(gid))); PUTBACK; rv = call_sv(MY_CXT.callback[11],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("chown end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_truncate (const char *file, off_t off) { int rv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("truncate begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(off))); #else if (asprintf(&temp, "%llu", off) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif PUTBACK; rv = call_sv(MY_CXT.callback[12],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("truncate end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_utime (const char *file, struct utimbuf *uti) { int rv; FUSE_CONTEXT_PRE; DEBUGf("utime begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(uti->actime))); XPUSHs(sv_2mortal(newSViv(uti->modtime))); PUTBACK; rv = call_sv(MY_CXT.callback[13],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("utime end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_open (const char *file, struct fuse_file_info *fi) { int rv; int flags = fi->flags; HV *fihash; FUSE_CONTEXT_PRE; DEBUGf("open begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(flags))); /* Create a hashref containing the details from fi * which we can look at or modify. */ fi->fh = 0; /* Ensure it starts with 0 - important if they don't set it */ fihash = newHV(); (void) hv_store(fihash, "direct_io", 9, newSViv(fi->direct_io), 0); (void) hv_store(fihash, "keep_cache", 10, newSViv(fi->keep_cache), 0); #if FUSE_VERSION >= 28 (void) hv_store(fihash, "nonseekable", 11, newSViv(fi->nonseekable), 0); #endif XPUSHs(sv_2mortal(newRV_noinc((SV*) fihash))); /* All hashref things done */ PUTBACK; /* Open called with filename, flags */ rv = call_sv(MY_CXT.callback[14],G_ARRAY); SPAGAIN; if(rv) { if(rv > 1) { FH_STOREHANDLE(fi,POPs); } rv = POPi; } else rv = 0; if (rv == 0) { /* Success, so copy the file handle which they returned */ SV **svp; if ((svp = hv_fetch(fihash, "direct_io", 9, 0)) != NULL) fi->direct_io = SvIV(*svp); if ((svp = hv_fetch(fihash, "keep_cache", 10, 0)) != NULL) fi->keep_cache = SvIV(*svp); #if FUSE_VERSION >= 28 if ((svp = hv_fetch(fihash, "nonseekable", 11, 0)) != NULL) fi->nonseekable = SvIV(*svp); #endif } FREETMPS; LEAVE; PUTBACK; DEBUGf("open end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_read (const char *file, char *buf, size_t buflen, off_t off, struct fuse_file_info *fi) { int rv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("read begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(sv_2mortal(newSViv(buflen))); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(off))); #else if (asprintf(&temp, "%llu", off) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[15],G_SCALAR); SPAGAIN; if(!rv) rv = -ENOENT; else { SV *mysv = POPs; if(SvTYPE(mysv) == SVt_NV || SvTYPE(mysv) == SVt_IV) rv = SvIV(mysv); else { if(SvPOK(mysv)) { rv = SvCUR(mysv); } else { rv = 0; } if(rv > buflen) croak("read() handler returned more than buflen! (%i > %i)",rv,buflen); if(rv) memcpy(buf,SvPV_nolen(mysv),rv); } } FREETMPS; LEAVE; PUTBACK; DEBUGf("read end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_write (const char *file, const char *buf, size_t buflen, off_t off, struct fuse_file_info *fi) { int rv; SV *sv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("write begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); #if (PERL_VERSION < 8) || (PERL_VERSION == 8 && PERL_SUBVERSION < 9) sv = newSV(0); sv_upgrade(sv, SVt_PV); #else sv = newSV_type(SVt_PV); #endif SvPV_set(sv, (char *)buf); SvLEN_set(sv, 0); SvCUR_set(sv, buflen); SvPOK_on(sv); SvREADONLY_on(sv); XPUSHs(sv_2mortal(sv)); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(off))); #else if (asprintf(&temp, "%llu", off) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[16],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("write end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_statfs (const char *file, struct statvfs *st) { int rv; FUSE_CONTEXT_PRE; DEBUGf("statfs begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); PUTBACK; rv = call_sv(MY_CXT.callback[17],G_ARRAY); SPAGAIN; DEBUGf("statfs got %i params\n",rv); if(rv == 6 || rv == 7) { st->f_bsize = POPi; st->f_bfree = POPi; st->f_blocks = POPi; st->f_ffree = POPi; st->f_files = POPi; st->f_namemax = POPi; /* zero and fill-in other */ st->f_fsid = 0; st->f_flag = 0; st->f_frsize = st->f_bsize; st->f_bavail = st->f_bfree; st->f_favail = st->f_ffree; if(rv == 7) rv = POPi; else rv = 0; } else if(rv > 1) croak("inappropriate number of returned values from statfs"); else if(rv) rv = POPi; else rv = -ENOSYS; FREETMPS; LEAVE; PUTBACK; DEBUGf("statfs end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_flush (const char *file, struct fuse_file_info *fi) { int rv; FUSE_CONTEXT_PRE; DEBUGf("flush begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[18],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("flush end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_release (const char *file, struct fuse_file_info *fi) { int rv; int flags = fi->flags; #if FUSE_VERSION >= 29 && !defined(PERL_HAS_64BITINT) char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("release begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(sv_2mortal(newSViv(flags))); XPUSHs(FH_GETHANDLE(fi)); #if FUSE_VERSION >= 29 XPUSHs(fi->flock_release ? sv_2mortal(newSViv(1)) : &PL_sv_undef); # ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(fi->lock_owner))); # else if (asprintf(&temp, "%llu", fi->lock_owner) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); # endif #endif PUTBACK; rv = call_sv(MY_CXT.callback[19],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FH_RELEASEHANDLE(fi); FREETMPS; LEAVE; PUTBACK; DEBUGf("release end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_fsync (const char *file, int datasync, struct fuse_file_info *fi) { int rv; int flags = fi->flags; FUSE_CONTEXT_PRE; DEBUGf("fsync begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(sv_2mortal(newSViv(flags))); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[20],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("fsync end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } #ifdef __APPLE__ int _PLfuse_setxattr (const char *file, const char *name, const char *buf, size_t buflen, int flags, uint32_t position) { #else int _PLfuse_setxattr (const char *file, const char *name, const char *buf, size_t buflen, int flags) { #endif int rv; FUSE_CONTEXT_PRE; DEBUGf("setxattr begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSVpv(name,0))); XPUSHs(sv_2mortal(newSVpvn(buf,buflen))); XPUSHs(sv_2mortal(newSViv(flags))); PUTBACK; rv = call_sv(MY_CXT.callback[21],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("setxattr end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } #ifdef __APPLE__ int _PLfuse_getxattr (const char *file, const char *name, char *buf, size_t buflen, uint32_t position) { #else int _PLfuse_getxattr (const char *file, const char *name, char *buf, size_t buflen) { #endif int rv; FUSE_CONTEXT_PRE; DEBUGf("getxattr begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSVpv(name,0))); PUTBACK; rv = call_sv(MY_CXT.callback[22],G_SCALAR); SPAGAIN; if(!rv) rv = -ENOENT; else { SV *mysv = POPs; rv = 0; if(SvTYPE(mysv) == SVt_NV || SvTYPE(mysv) == SVt_IV) rv = SvIV(mysv); else { if(SvPOK(mysv)) { rv = SvCUR(mysv); } else { rv = 0; } if ((rv > 0) && (buflen > 0)) { if(rv > buflen) rv = -ERANGE; else memcpy(buf,SvPV_nolen(mysv),rv); } } } FREETMPS; LEAVE; PUTBACK; DEBUGf("getxattr end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_listxattr (const char *file, char *list, size_t size) { int prv, rv; FUSE_CONTEXT_PRE; DEBUGf("listxattr begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); PUTBACK; prv = call_sv(MY_CXT.callback[23],G_ARRAY); SPAGAIN; if(!prv) rv = -ENOENT; else { char *p = list; int spc = size; int total_len = 0; rv = POPi; prv--; /* Always nul terminate */ if (list && (size > 0)) list[0] = '\0'; while (prv > 0) { SV *mysv = POPs; prv--; if (SvPOK(mysv)) { /* Copy nul too */ int s = SvCUR(mysv) + 1; total_len += s; if (p && (size > 0) && (spc >= s)) { memcpy(p,SvPV_nolen(mysv),s); p += s; spc -= s; } } } /* * If the Perl returned an error, return that. * Otherwise check that the buffer was big enough. */ if (rv == 0) { rv = total_len; if ((size > 0) && (size < total_len)) rv = -ERANGE; } } FREETMPS; LEAVE; PUTBACK; DEBUGf("listxattr end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_removexattr (const char *file, const char *name) { int rv; FUSE_CONTEXT_PRE; DEBUGf("removexattr begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSVpv(name,0))); PUTBACK; rv = call_sv(MY_CXT.callback[24],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("removexattr end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_opendir(const char *file, struct fuse_file_info *fi) { int rv; FUSE_CONTEXT_PRE; DEBUGf("opendir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); fi->fh = 0; /* Ensure it starts with 0 - important if they don't set it */ PUTBACK; rv = call_sv(MY_CXT.callback[25], G_ARRAY); SPAGAIN; if (rv) { if (rv > 1) { FH_STOREHANDLE(fi, POPs); } rv = POPi; } else rv = 0; FREETMPS; LEAVE; PUTBACK; DEBUGf("opendir end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_readdir(const char *file, void *dirh, fuse_fill_dir_t dirfil, off_t off, struct fuse_file_info *fi) { int prv = 0, rv; SV *sv, **svp, **swp; AV *av, *av2; struct stat st; bool st_filled = 0; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("readdir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(off))); #else if (asprintf(&temp, "%llu", off) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif XPUSHs(FH_GETHANDLE(fi)); PUTBACK; prv = call_sv(MY_CXT.callback[26],G_ARRAY); SPAGAIN; if (prv) { /* Should yield the bottom of the current stack... */ swp = SP - prv + 1; rv = POPi; memset(&st, 0, sizeof(struct stat)); /* Sort of a hack to walk the stack in order, instead of reverse * order - trying to explain to potential users why they need to * reverse the order of this array would be confusing, at best. */ while (swp <= &TOPs) { sv = *(swp++); if (!SvROK(sv) && SvPOK(sv)) /* Just a bare SV (probably a string; hopefully a string) */ dirfil(dirh, SvPVx_nolen(sv), NULL, 0); else if (SvROK(sv) && SvTYPE(av = (AV *)SvRV(sv)) == SVt_PVAV) { if (av_len(av) >= 2) { /* The third element of the array should be the args that * would otherwise go to getattr(); a lot of filesystems * will, or at least can, return that info as part of the * enumeration process... */ svp = av_fetch(av, 2, FALSE); if (SvROK(*svp) && SvTYPE(av2 = (AV *)SvRV(*svp)) == SVt_PVAV && av_len(av2) == 12) { st.st_dev = SvIV(*(av_fetch(av2, 0, FALSE))); st.st_ino = SvIV(*(av_fetch(av2, 1, FALSE))); st.st_mode = SvIV(*(av_fetch(av2, 2, FALSE))); st.st_nlink = SvIV(*(av_fetch(av2, 3, FALSE))); st.st_uid = SvIV(*(av_fetch(av2, 4, FALSE))); st.st_gid = SvIV(*(av_fetch(av2, 5, FALSE))); st.st_rdev = SvIV(*(av_fetch(av2, 6, FALSE))); st.st_size = SvNV(*(av_fetch(av2, 7, FALSE))); PULL_TIME(&st, st_atim, *(av_fetch(av2, 8, FALSE))); PULL_TIME(&st, st_mtim, *(av_fetch(av2, 9, FALSE))); PULL_TIME(&st, st_ctim, *(av_fetch(av2, 10, FALSE))); st.st_blksize = SvIV(*(av_fetch(av2, 11, FALSE))); st.st_blocks = SvIV(*(av_fetch(av2, 12, FALSE))); st_filled = 1; } else fprintf(stderr,"Extra SV didn't appear to be correct, ignoring\n"); /* For now if the element isn't what we want, just * quietly ignore it... */ } if (av_len(av) >= 1) { char *entryname = SvPVx_nolen(*(av_fetch(av, 1, FALSE))); off_t elemnum = SvNV(*(av_fetch(av, 0, FALSE))); dirfil(dirh, entryname, st_filled ? &st : NULL, elemnum); } if (st_filled) { memset(&st, 0, sizeof(struct stat)); st_filled = 0; } } else fprintf(stderr, "ERROR: Unknown entry passed via readdir\n"); } SP -= prv - 1; } else { fprintf(stderr,"readdir() handler returned nothing!\n"); rv = -ENOSYS; } FREETMPS; LEAVE; PUTBACK; DEBUGf("readdir end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_releasedir(const char *file, struct fuse_file_info *fi) { int rv; FUSE_CONTEXT_PRE; DEBUGf("releasedir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[27], G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FH_RELEASEHANDLE(fi); FREETMPS; LEAVE; PUTBACK; DEBUGf("releasedir end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_fsyncdir(const char *file, int datasync, struct fuse_file_info *fi) { int rv; FUSE_CONTEXT_PRE; DEBUGf("fsyncdir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(sv_2mortal(newSViv(datasync))); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[28], G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("fsyncdir end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } void *_PLfuse_init(struct fuse_conn_info *fc) { void *rv = NULL; int prv; FUSE_CONTEXT_PRE; DEBUGf("init begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); PUTBACK; prv = call_sv(MY_CXT.callback[29], G_SCALAR); SPAGAIN; if (prv) { rv = POPs; if (rv == &PL_sv_undef) rv = NULL; else rv = SvREFCNT_inc((SV *)rv); } FREETMPS; LEAVE; PUTBACK; DEBUGf("init end: %p\n", rv); FUSE_CONTEXT_POST; return rv; } void _PLfuse_destroy(void *private_data) { FUSE_CONTEXT_PRE; DEBUGf("destroy begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(private_data ? (SV *)private_data : &PL_sv_undef); PUTBACK; call_sv(MY_CXT.callback[30], G_VOID); SPAGAIN; if (private_data) SvREFCNT_dec((SV *)private_data); FREETMPS; LEAVE; PUTBACK; DEBUGf("init end\n"); FUSE_CONTEXT_POST; } int _PLfuse_access(const char *file, int mask) { int rv; FUSE_CONTEXT_PRE; DEBUGf("access begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(mask))); PUTBACK; rv = call_sv(MY_CXT.callback[31], G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("access end: %d\n", rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_create(const char *file, mode_t mode, struct fuse_file_info *fi) { int rv; HV *fihash; FUSE_CONTEXT_PRE; DEBUGf("create begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(mode))); XPUSHs(sv_2mortal(newSViv(fi->flags))); fi->fh = 0; /* Ensure it starts with 0 - important if they don't set it */ /* Create a hashref containing the details from fi * which we can look at or modify. */ fihash = newHV(); (void) hv_store(fihash, "direct_io", 9, newSViv(fi->direct_io), 0); (void) hv_store(fihash, "keep_cache", 10, newSViv(fi->keep_cache), 0); #if FUSE_VERSION >= 28 (void) hv_store(fihash, "nonseekable", 11, newSViv(fi->nonseekable), 0); #endif XPUSHs(sv_2mortal(newRV_noinc((SV*) fihash))); /* All hashref things done */ PUTBACK; rv = call_sv(MY_CXT.callback[32], G_ARRAY); SPAGAIN; if (rv) { if (rv > 1) { FH_STOREHANDLE(fi,POPs); } rv = POPi; } else { fprintf(stderr, "create() handler returned nothing!\n"); rv = -ENOSYS; } if (rv == 0) { /* Success, so copy the file handle which they returned */ SV **svp; if ((svp = hv_fetch(fihash, "direct_io", 9, 0)) != NULL) fi->direct_io = SvIV(*svp); if ((svp = hv_fetch(fihash, "keep_cache", 10, 0)) != NULL) fi->keep_cache = SvIV(*svp); #if FUSE_VERSION >= 28 if ((svp = hv_fetch(fihash, "nonseekable", 11, 0)) != NULL) fi->nonseekable = SvIV(*svp); #endif } FREETMPS; LEAVE; PUTBACK; DEBUGf("create end: %d\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_ftruncate(const char *file, off_t off, struct fuse_file_info *fi) { int rv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("ftruncate begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(off))); #else if (asprintf(&temp, "%llu", off) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[33],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("ftruncate end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_fgetattr(const char *file, struct stat *result, struct fuse_file_info *fi) { int rv; FUSE_CONTEXT_PRE; DEBUGf("fgetattr begin: %s\n",file); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[34],G_ARRAY); SPAGAIN; if(rv != 13) { if(rv > 1) { fprintf(stderr,"inappropriate number of returned values from getattr\n"); rv = -ENOSYS; } else if(rv) rv = POPi; else rv = -ENOENT; } else { result->st_blocks = POPi; result->st_blksize = POPi; PULL_TIME(result, st_ctim, POPs); PULL_TIME(result, st_mtim, POPs); PULL_TIME(result, st_atim, POPs); result->st_size = POPn; // we pop double here to support files larger than 4Gb (long limit) result->st_rdev = POPi; result->st_gid = POPi; result->st_uid = POPi; result->st_nlink = POPi; result->st_mode = POPi; result->st_ino = POPi; result->st_dev = POPi; rv = 0; } FREETMPS; LEAVE; PUTBACK; DEBUGf("fgetattr end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_lock(const char *file, struct fuse_file_info *fi, int cmd, struct flock *lockinfo) { int rv; HV *lihash; SV *sv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("lock begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(sv_2mortal(newSViv(cmd))); lihash = newHV(); if (lockinfo) { (void) hv_store(lihash, "l_type", 6, newSViv(lockinfo->l_type), 0); (void) hv_store(lihash, "l_whence", 8, newSViv(lockinfo->l_whence), 0); #ifdef PERL_HAS_64BITINT sv = newSViv(lockinfo->l_start); #else if (asprintf(&temp, "%llu", lockinfo->l_start) == -1) croak("Memory allocation failure!"); sv = newSVpv(temp, 0); free(temp); #endif (void) hv_store(lihash, "l_start", 7, sv, 0); #ifdef PERL_HAS_64BITINT sv = newSViv(lockinfo->l_len); #else if (asprintf(&temp, "%llu", lockinfo->l_len) == -1) croak("Memory allocation failure!"); sv = newSVpv(temp, 0); free(temp); #endif (void) hv_store(lihash, "l_len", 5, sv, 0); (void) hv_store(lihash, "l_pid", 5, newSViv(lockinfo->l_pid), 0); } XPUSHs(sv_2mortal(newRV_noinc((SV*) lihash))); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[35],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); if (lockinfo && !rv) { /* Need to copy back any altered values from the hash into * the struct... */ SV **svp; if ((svp = hv_fetch(lihash, "l_type", 6, 0)) != NULL) lockinfo->l_type = SvIV(*svp); if ((svp = hv_fetch(lihash, "l_whence", 8, 0)) != NULL) lockinfo->l_whence = SvIV(*svp); if ((svp = hv_fetch(lihash, "l_start", 7, 0)) != NULL) lockinfo->l_start = SvNV(*svp); if ((svp = hv_fetch(lihash, "l_len", 5, 0)) != NULL) lockinfo->l_len = SvNV(*svp); if ((svp = hv_fetch(lihash, "l_pid", 5, 0)) != NULL) lockinfo->l_pid = SvIV(*svp); } FREETMPS; LEAVE; PUTBACK; DEBUGf("lock end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_utimens(const char *file, const struct timespec tv[2]) { int rv; FUSE_CONTEXT_PRE; DEBUGf("utimens begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); if (MY_CXT.utimens_as_array) { /* Pushing timespecs as 2-element arrays (if tv is present). */ AV *av; if (tv) { av = newAV(); av_push(av, newSViv(tv[0].tv_sec)); av_push(av, newSViv(tv[0].tv_nsec)); XPUSHs(sv_2mortal(newRV_noinc((SV *)av))); av = newAV(); av_push(av, newSViv(tv[1].tv_sec)); av_push(av, newSViv(tv[1].tv_nsec)); XPUSHs(sv_2mortal(newRV_noinc((SV *)av))); } else { XPUSHs(&PL_sv_undef); XPUSHs(&PL_sv_undef); } } else { /* Pushing timespecs as floating point (double) values. */ XPUSHs(tv ? sv_2mortal(newSVnv(tv[0].tv_sec + (tv[0].tv_nsec / 1000000000.0))) : &PL_sv_undef); XPUSHs(tv ? sv_2mortal(newSVnv(tv[1].tv_sec + (tv[1].tv_nsec / 1000000000.0))) : &PL_sv_undef); } PUTBACK; rv = call_sv(MY_CXT.callback[36],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("utimens end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_bmap(const char *file, size_t blocksize, uint64_t *idx) { int rv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("bmap begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(blocksize))); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(*idx))); #else if (asprintf(&temp, "%llu", *idx) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif PUTBACK; rv = call_sv(MY_CXT.callback[37],G_ARRAY); SPAGAIN; if (rv > 0 && rv < 3) { if (rv == 2) *idx = POPn; rv = POPi; } else { fprintf(stderr, "bmap(): wrong number of values returned?\n"); rv = -ENOSYS; } FREETMPS; LEAVE; PUTBACK; DEBUGf("bmap end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } #if FUSE_VERSION >= 28 # ifndef __linux__ # define _IOC_SIZE(n) IOCPARM_LEN(n) # endif int _PLfuse_ioctl(const char *file, int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, void *data) { int rv; SV *sv = NULL; FUSE_CONTEXT_PRE; DEBUGf("ioctl begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); /* I don't know why cmd is a signed int in the first place; * casting as unsigned so stupid tricks don't have to be done on * the perl side */ XPUSHs(sv_2mortal(newSVuv((unsigned int)cmd))); XPUSHs(sv_2mortal(newSViv(flags))); if (cmd & IOC_IN) XPUSHs(sv_2mortal(newSVpvn(data, _IOC_SIZE(cmd)))); else XPUSHs(&PL_sv_undef); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[39],G_ARRAY); SPAGAIN; if ((cmd & IOC_OUT) && (rv == 2)) { sv = POPs; rv--; } if (rv > 0) rv = POPi; if ((cmd & IOC_OUT) && !rv) { if (sv) { size_t len; char *rdata = SvPV(sv, len); if (len > _IOC_SIZE(cmd)) { fprintf(stderr, "ioctl(): returned data was too large for data area\n"); rv = -EFBIG; } else { memset(data, 0, _IOC_SIZE(cmd)); memcpy(data, rdata, len); } } else { fprintf(stderr, "ioctl(): ioctl was a read op, but no data was returned from call?\n"); rv = -EFAULT; } } FREETMPS; LEAVE; PUTBACK; DEBUGf("ioctl end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_poll(const char *file, struct fuse_file_info *fi, struct fuse_pollhandle *ph, unsigned *reventsp) { int rv; SV *sv = NULL; FUSE_CONTEXT_PRE; DEBUGf("poll begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); if (ph) { /* Still gotta figure out how to do this right... */ sv = newSViv(PTR2IV(ph)); SvREADONLY_on(sv); SvSHARE(sv); XPUSHs(sv); } else XPUSHs(&PL_sv_undef); XPUSHs(sv_2mortal(newSViv(*reventsp))); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[40],G_ARRAY); SPAGAIN; if (rv > 1) { *reventsp = POPi; rv--; } rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("poll end: %i\n", rv); FUSE_CONTEXT_POST; return rv; } #endif /* FUSE_VERSION >= 28 */ #if FUSE_VERSION >= 29 int _PLfuse_write_buf (const char *file, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *fi) { int rv, i; HV *bvhash; AV *bvlist; SV *sv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("write_buf begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(off))); #else if (asprintf(&temp, "%llu", off) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif bvlist = newAV(); for (i = 0; i < buf->count; i++) { bvhash = newHV(); sv = newSViv(buf->buf[i].size); (void) hv_store(bvhash, "size", 4, sv, 0); sv = newSViv(buf->buf[i].flags); (void) hv_store(bvhash, "flags", 5, sv, 0); sv = &PL_sv_undef; if (!(buf->buf[i].flags & FUSE_BUF_IS_FD)) { #if (PERL_VERSION < 8) || (PERL_VERSION == 8 && PERL_SUBVERSION < 9) sv = newSV(0); sv_upgrade(sv, SVt_PV); #else sv = newSV_type(SVt_PV); #endif SvPV_set(sv, (char *)buf->buf[i].mem); SvLEN_set(sv, 0); SvCUR_set(sv, buf->buf[i].size); SvPOK_on(sv); SvREADONLY_on(sv); } (void) hv_store(bvhash, "mem", 3, sv, 0); sv = newSViv(buf->buf[i].fd); (void) hv_store(bvhash, "fd", 2, sv, 0); sv = newSViv(buf->buf[i].pos); (void) hv_store(bvhash, "pos", 3, sv, 0); av_push(bvlist, newRV((SV *)bvhash)); } XPUSHs(sv_2mortal(newRV_noinc((SV *)bvlist))); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[41], G_SCALAR); SPAGAIN; rv = rv ? POPi : -ENOENT; FREETMPS; LEAVE; PUTBACK; DEBUGf("write_buf end: %i\n", rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_read_buf (const char *file, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *fi) { int rv; HV *bvhash; AV *bvlist; struct fuse_bufvec *src; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("read_buf begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(sv_2mortal(newSViv(size))); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(off))); #else if (asprintf(&temp, "%llu", off) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif bvlist = newAV(); bvhash = newHV(); (void) hv_store(bvhash, "size", 4, newSViv(size), 0); (void) hv_store(bvhash, "flags", 5, newSViv(0), 0); (void) hv_store(bvhash, "mem", 3, newSVpv("", 0), 0); (void) hv_store(bvhash, "fd", 2, newSViv(-1), 0); (void) hv_store(bvhash, "pos", 3, newSViv(0), 0); av_push(bvlist, newRV((SV *)bvhash)); XPUSHs(sv_2mortal(newRV_noinc((SV*) bvlist))); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[42], G_SCALAR); SPAGAIN; if (!rv) rv = -ENOENT; else { SV **svp; int i; rv = POPi; if (rv < 0) goto READ_BUF_FAIL; src = malloc(sizeof(struct fuse_bufvec) + (av_len(bvlist) * sizeof(struct fuse_buf))); if (src == NULL) croak("Memory allocation failure!"); *src = FUSE_BUFVEC_INIT(0); src->count = av_len(bvlist) + 1; for (i = 0; i <= av_len(bvlist); i++) { svp = av_fetch(bvlist, i, 1); if (svp == NULL || *svp == NULL || !SvROK(*svp) || (bvhash = (HV *)SvRV(*svp)) == NULL || SvTYPE((SV *)bvhash) != SVt_PVHV) croak("Entry provided as part of bufvec was wrong!"); if ((svp = hv_fetch(bvhash, "size", 4, 0)) != NULL) src->buf[i].size = SvIV(*svp); if ((svp = hv_fetch(bvhash, "flags", 5, 0)) != NULL) src->buf[i].flags = SvIV(*svp); if (src->buf[i].flags & FUSE_BUF_IS_FD) { if ((svp = hv_fetch(bvhash, "fd", 2, 0)) != NULL) src->buf[i].fd = SvIV(*svp); else croak("FUSE_BUF_IS_FD passed but no fd!"); if (src->buf[i].flags & FUSE_BUF_FD_SEEK) { if ((svp = hv_fetch(bvhash, "pos", 3, 0)) != NULL) src->buf[i].fd = SvIV(*svp); else croak("FUSE_BUF_FD_SEEK passed but no pos!"); } } else { if ((svp = hv_fetch(bvhash, "mem", 3, 0)) != NULL) { src->buf[i].mem = SvPV_nolen(*svp); /* Should keep Perl from free()ing the memory * zone the SV points to, since it'll be * free()'d elsewhere at (potentially) any * time... */ SvLEN_set(*svp, 0); } } } *bufp = src; } READ_BUF_FAIL: FREETMPS; LEAVE; PUTBACK; DEBUGf("read_buf end: %i\n", rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_flock (const char *file, struct fuse_file_info *fi, int op) { int rv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("flock begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(FH_GETHANDLE(fi)); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(fi->lock_owner))); #else if (asprintf(&temp, "%llu", fi->lock_owner) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); #endif XPUSHs(sv_2mortal(newSViv(op))); PUTBACK; rv = call_sv(MY_CXT.callback[43],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("flock end: %i\n", rv); FUSE_CONTEXT_POST; return rv; } #if FUSE_FOUND_MICRO_VER >= 1 int _PLfuse_fallocate (const char *file, int mode, off_t offset, off_t length, struct fuse_file_info *fi) { int rv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("fallocate begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(FH_GETHANDLE(fi)); XPUSHs(sv_2mortal(newSViv(mode))); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(offset))); #else if (asprintf(&temp, "%llu", offset) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); #endif #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(length))); #else if (asprintf(&temp, "%llu", length) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); #endif PUTBACK; rv = call_sv(MY_CXT.callback[44],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("fallocate end: %i\n", rv); FUSE_CONTEXT_POST; return rv; } #endif /* FUSE_FOUND_MICRO_VER >= 1 */ #endif /* FUSE_VERSION >= 29 */ struct fuse_operations _available_ops = { .getattr = _PLfuse_getattr, .readlink = _PLfuse_readlink, .getdir = _PLfuse_getdir, .mknod = _PLfuse_mknod, .mkdir = _PLfuse_mkdir, .unlink = _PLfuse_unlink, .rmdir = _PLfuse_rmdir, .symlink = _PLfuse_symlink, .rename = _PLfuse_rename, .link = _PLfuse_link, .chmod = _PLfuse_chmod, .chown = _PLfuse_chown, .truncate = _PLfuse_truncate, .utime = _PLfuse_utime, .open = _PLfuse_open, .read = _PLfuse_read, .write = _PLfuse_write, .statfs = _PLfuse_statfs, .flush = _PLfuse_flush, .release = _PLfuse_release, .fsync = _PLfuse_fsync, .setxattr = _PLfuse_setxattr, .getxattr = _PLfuse_getxattr, .listxattr = _PLfuse_listxattr, .removexattr = _PLfuse_removexattr, .opendir = _PLfuse_opendir, .readdir = _PLfuse_readdir, .releasedir = _PLfuse_releasedir, .fsyncdir = _PLfuse_fsyncdir, .init = _PLfuse_init, .destroy = _PLfuse_destroy, .access = _PLfuse_access, .create = _PLfuse_create, .ftruncate = _PLfuse_ftruncate, .fgetattr = _PLfuse_fgetattr, .lock = _PLfuse_lock, .utimens = _PLfuse_utimens, .bmap = _PLfuse_bmap, #if FUSE_VERSION >= 28 .ioctl = _PLfuse_ioctl, .poll = _PLfuse_poll, #endif /* FUSE_VERSION >= 28 */ #if FUSE_VERSION >= 29 .write_buf = _PLfuse_write_buf, .read_buf = _PLfuse_read_buf, .flock = _PLfuse_flock, #if FUSE_FOUND_MICRO_VER >= 1 .fallocate = _PLfuse_fallocate, #endif /* FUSE_FOUND_MICRO_VER >= 1 */ #endif /* FUSE_VERSION >= 29 */ }; MODULE = Fuse PACKAGE = Fuse PROTOTYPES: DISABLE BOOT: MY_CXT_INIT; #ifdef USE_ITHREADS MY_CXT.self = aTHX; #endif void CLONE(...) PREINIT: #ifdef USE_ITHREADS int i; dTHX; #endif CODE: #ifdef USE_ITHREADS MY_CXT_CLONE; tTHX parent = MY_CXT.self; MY_CXT.self = my_perl; #if (PERL_VERSION < 10) || (PERL_VERSION == 10 && PERL_SUBVERSION <= 0) /* CLONE entered without a pointer table, so we can't safely clone static data */ if(!PL_ptr_table) { for(i=0;i 13) || (PERL_VERSION == 13 && PERL_SUBVERSION >= 2) clone_param = Perl_clone_params_new(parent, aTHX); #else CLONE_PARAMS raw_param; raw_param.flags = 0; raw_param.proto_perl = parent; raw_param.stashes = (AV*)sv_2mortal((SV*)newAV()); clone_param = &raw_param; #endif for(i=0;i 13) || (PERL_VERSION == 13 && PERL_SUBVERSION >= 2) Perl_clone_params_del(clone_param); #endif } #endif SV* fuse_get_context() PREINIT: #ifndef __OpenBSD__ struct fuse_context *fc; #endif /* !defined(__OpenBSD__) */ CODE: #ifndef __OpenBSD__ fc = fuse_get_context(); if(fc) { HV *hash = newHV(); (void) hv_store(hash, "uid", 3, newSViv(fc->uid), 0); (void) hv_store(hash, "gid", 3, newSViv(fc->gid), 0); (void) hv_store(hash, "pid", 3, newSViv(fc->pid), 0); if (fc->private_data) (void) hv_store(hash, "private", 7, fc->private_data, 0); #if FUSE_VERSION >= 28 (void) hv_store(hash, "umask", 5, newSViv(fc->umask), 0); #endif /* FUSE_VERSION >= 28 */ RETVAL = newRV_noinc((SV*)hash); } else { #endif /* !defined(__OpenBSD__) */ XSRETURN_UNDEF; #ifndef __OpenBSD__ } #endif /* !defined(__OpenBSD__) */ OUTPUT: RETVAL void fuse_version() PPCODE: int gimme = GIMME_V; if (gimme == G_SCALAR) XPUSHs(sv_2mortal(newSVpvf("%d.%d", FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION))); else if (gimme == G_ARRAY) { #ifdef FUSE_FOUND_MICRO_VER XPUSHs(sv_2mortal(newSViv(FUSE_FOUND_MAJOR_VER))); XPUSHs(sv_2mortal(newSViv(FUSE_FOUND_MINOR_VER))); XPUSHs(sv_2mortal(newSViv(FUSE_FOUND_MICRO_VER))); #else XPUSHs(sv_2mortal(newSViv(FUSE_MAJOR_VERSION))); XPUSHs(sv_2mortal(newSViv(FUSE_MINOR_VERSION))); XPUSHs(sv_2mortal(newSViv(0))); #endif } SV * XATTR_CREATE() CODE: RETVAL = newSViv(XATTR_CREATE); OUTPUT: RETVAL SV * XATTR_REPLACE() CODE: RETVAL = newSViv(XATTR_REPLACE); OUTPUT: RETVAL #if FUSE_VERSION >= 29 #ifdef __linux__ SV * UTIME_NOW() CODE: RETVAL = newSViv(UTIME_NOW); OUTPUT: RETVAL SV * UTIME_OMIT() CODE: RETVAL = newSViv(UTIME_OMIT); OUTPUT: RETVAL #endif /* defined(__linux__) */ SV * FUSE_BUF_IS_FD() CODE: RETVAL = newSViv(FUSE_BUF_IS_FD); OUTPUT: RETVAL SV * FUSE_BUF_FD_SEEK() CODE: RETVAL = newSViv(FUSE_BUF_FD_SEEK); OUTPUT: RETVAL SV * FUSE_BUF_FD_RETRY() CODE: RETVAL = newSViv(FUSE_BUF_FD_RETRY); OUTPUT: RETVAL ssize_t fuse_buf_copy(...) PREINIT: struct fuse_bufvec *dst = NULL, *src = NULL; AV *av_src, *av_dst; HV *hv; SV **svp, *sv; int i; INIT: if (items != 2) { fprintf(stderr, "fuse_buf_copy needs dst and src\n"); XSRETURN_UNDEF; } CODE: sv = ST(0); if (!(SvROK(sv) && SvTYPE(av_dst = (AV *)SvRV(sv)) == SVt_PVAV)) croak("Argument supplied was not arrayref!"); sv = ST(1); if (!(SvROK(sv) && SvTYPE(av_src = (AV *)SvRV(sv)) == SVt_PVAV)) croak("Argument supplied was not arrayref!"); dst = malloc(sizeof(struct fuse_bufvec) + (av_len(av_dst) * sizeof(struct fuse_buf))); if (dst == NULL) croak("Memory allocation failure!"); *dst = FUSE_BUFVEC_INIT(0); dst->count = av_len(av_dst) + 1; for (i = 0; i <= av_len(av_dst); i++) { svp = av_fetch(av_dst, i, 1); if (svp == NULL || *svp == NULL || !SvROK(*svp) || (hv = (HV *)SvRV(*svp)) == NULL || SvTYPE((SV *)hv) != SVt_PVHV) croak("Entry provided as part of bufvec was wrong!"); if ((svp = hv_fetch(hv, "size", 4, 0)) != NULL) dst->buf[i].size = SvIV(*svp); if ((svp = hv_fetch(hv, "flags", 5, 0)) != NULL) dst->buf[i].flags = SvIV(*svp); if (dst->buf[i].flags & FUSE_BUF_IS_FD) { if ((svp = hv_fetch(hv, "fd", 2, 0)) != NULL) dst->buf[i].fd = SvIV(*svp); else croak("FUSE_BUF_IS_FD passed but no fd!"); if (dst->buf[i].flags & FUSE_BUF_FD_SEEK) { if ((svp = hv_fetch(hv, "pos", 3, 0)) != NULL) dst->buf[i].fd = SvIV(*svp); else croak("FUSE_BUF_FD_SEEK passed but no pos!"); } } else { if ((svp = hv_fetch(hv, "mem", 3, 0)) != NULL) { if ((dst->buf[i].mem = malloc(dst->buf[i].size)) == NULL) croak("Memory allocation failure!"); } } } src = malloc(sizeof(struct fuse_bufvec) + (av_len(av_src) * sizeof(struct fuse_buf))); if (src == NULL) croak("Memory allocation failure!"); *src = FUSE_BUFVEC_INIT(0); src->count = av_len(av_src) + 1; for (i = 0; i <= av_len(av_src); i++) { svp = av_fetch(av_src, i, 1); if (svp == NULL || *svp == NULL || !SvROK(*svp) || (hv = (HV *)SvRV(*svp)) == NULL || SvTYPE((SV *)hv) != SVt_PVHV) croak("Entry provided as part of bufvec was wrong!"); if ((svp = hv_fetch(hv, "size", 4, 0)) != NULL) src->buf[i].size = SvIV(*svp); if ((svp = hv_fetch(hv, "flags", 5, 0)) != NULL) src->buf[i].flags = SvIV(*svp); if (src->buf[i].flags & FUSE_BUF_IS_FD) { if ((svp = hv_fetch(hv, "fd", 2, 0)) != NULL) src->buf[i].fd = SvIV(*svp); else croak("FUSE_BUF_IS_FD passed but no fd!"); if (src->buf[i].flags & FUSE_BUF_FD_SEEK) { if ((svp = hv_fetch(hv, "pos", 3, 0)) != NULL) src->buf[i].fd = SvIV(*svp); else croak("FUSE_BUF_FD_SEEK passed but no pos!"); } } else { if ((svp = hv_fetch(hv, "mem", 3, 0)) != NULL) { src->buf[i].mem = SvPV_nolen(*svp); SvLEN_set(*svp, 0); } } } RETVAL = fuse_buf_copy(dst, src, 0); if (RETVAL > 0) { for (i = 0; i < dst->count; i++) { svp = av_fetch(av_dst, i, 1); if (svp == NULL || *svp == NULL || !SvROK(*svp) || (hv = (HV *)SvRV(*svp)) == NULL || SvTYPE((SV *)hv) != SVt_PVHV) croak("Entry provided as part of bufvec was wrong!"); if (!(dst->buf[i].flags & FUSE_BUF_IS_FD)) { #if (PERL_VERSION < 8) || (PERL_VERSION == 8 && PERL_SUBVERSION < 9) sv = newSV(0); sv_upgrade(sv, SVt_PV); #else sv = newSV_type(SVt_PV); #endif SvPV_set(sv, (char *)dst->buf[i].mem); SvLEN_set(sv, dst->buf[i].size); SvCUR_set(sv, dst->buf[i].size); SvPOK_on(sv); SvREADONLY_on(sv); (void) hv_store(hv, "mem", 3, sv, 0); } } } free(dst); free(src); OUTPUT: RETVAL #endif /* FUSE_VERSION >= 29 */ void perl_fuse_main(...) PREINIT: struct fuse_operations fops; int i, debug; char *mountpoint; char *mountopts; #ifndef __OpenBSD__ struct fuse_args args = FUSE_ARGS_INIT(0, NULL); #endif /* !defined(__OpenBSD__) */ struct fuse_chan *fc; dMY_CXT; INIT: if(items != N_CALLBACKS + N_FLAGS) { fprintf(stderr,"Perl<->C inconsistency or internal error\n"); XSRETURN_UNDEF; } memset(&fops, 0, sizeof(struct fuse_operations)); CODE: debug = SvIV(ST(0)); MY_CXT.threaded = SvIV(ST(1)); MY_CXT.handles = (HV*)(sv_2mortal((SV*)(newHV()))); if(MY_CXT.threaded) { #ifdef FUSE_USE_ITHREADS master_interp = aTHX; MUTEX_INIT(&MY_CXT.mutex); SvSHARE((SV*)(MY_CXT.handles)); #else fprintf(stderr,"FUSE warning: Your script has requested multithreaded " "mode, but your perl was not built with a supported " "thread model. Threads are disabled.\n"); MY_CXT.threaded = 0; #endif } mountpoint = SvPV_nolen(ST(2)); mountopts = SvPV_nolen(ST(3)); #if FUSE_VERSION >= 28 fops.flag_nullpath_ok = SvIV(ST(4)); #endif /* FUSE_VERSION >= 28 */ MY_CXT.utimens_as_array = SvIV(ST(5)); #if FUSE_VERSION >= 29 fops.flag_nopath = SvIV(ST(6)); fops.flag_utime_omit_ok = SvIV(ST(7)); #endif /* FUSE_VERSION >= 29 */ for(i=0;i= 28 void pollhandle_destroy(...) PREINIT: struct fuse_pollhandle *ph; INIT: if (items != 1) { fprintf(stderr, "No pollhandle passed?\n"); XSRETURN_UNDEF; } CODE: ph = INT2PTR(struct fuse_pollhandle*, SvIV(ST(0))); fuse_pollhandle_destroy(ph); int notify_poll(...) PREINIT: struct fuse_pollhandle *ph; INIT: if (items != 1) { fprintf(stderr, "No pollhandle passed?\n"); XSRETURN_UNDEF; } CODE: ph = INT2PTR(struct fuse_pollhandle*, SvIV(ST(0))); RETVAL = fuse_notify_poll(ph); OUTPUT: RETVAL #endif