From 06dd8ebb4bc96acd173c00b24251546d74762990 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Thu, 11 Nov 2004 14:44:15 +0000 Subject: [PATCH] Initial revision git-svn-id: svn+ssh://llin/home/dpavlin/private/svn/fuse/perl/trunk@4 6e4b0b00-1209-0410-87b2-b275959b5705 --- .cvsignore | 1 + AUTHORS | 4 + Changes | 12 + Fuse.pm | 360 ++++++++++++++++++++++++ Fuse.xs | 572 ++++++++++++++++++++++++++++++++++++++ MANIFEST | 7 + Makefile.PL | 17 ++ README | 69 +++++ examples/example.pl | 90 ++++++ examples/loopback.pl | 136 +++++++++ examples/rmount.pl | 82 ++++++ examples/rmount_remote.pl | 143 ++++++++++ test.pl | 8 + test/chmod.t | 11 + test/chown.t | 14 + test/getattr.t | 42 +++ test/getdir.t | 33 +++ test/helper.pm | 23 ++ test/link.t | 16 ++ test/mkdir.t | 11 + test/mknod.t | 37 +++ test/open.t | 10 + test/read.t | 13 + test/readlink.t | 11 + test/rename.t | 12 + test/rmdir.t | 13 + test/s/mount.t | 25 ++ test/s/umount.t | 7 + test/statfs.t | 21 ++ test/symlink.t | 19 ++ test/test-template | 5 + test/truncate.t | 12 + test/unlink.t | 14 + test/utime.t | 13 + test/write.t | 45 +++ 35 files changed, 1908 insertions(+) create mode 100644 .cvsignore create mode 100644 AUTHORS create mode 100644 Changes create mode 100644 Fuse.pm create mode 100644 Fuse.xs create mode 100644 MANIFEST create mode 100644 Makefile.PL create mode 100644 README create mode 100644 examples/example.pl create mode 100644 examples/loopback.pl create mode 100644 examples/rmount.pl create mode 100644 examples/rmount_remote.pl create mode 100644 test.pl create mode 100644 test/chmod.t create mode 100644 test/chown.t create mode 100644 test/getattr.t create mode 100644 test/getdir.t create mode 100644 test/helper.pm create mode 100644 test/link.t create mode 100644 test/mkdir.t create mode 100644 test/mknod.t create mode 100644 test/open.t create mode 100644 test/read.t create mode 100644 test/readlink.t create mode 100644 test/rename.t create mode 100644 test/rmdir.t create mode 100644 test/s/mount.t create mode 100644 test/s/umount.t create mode 100644 test/statfs.t create mode 100644 test/symlink.t create mode 100644 test/test-template create mode 100644 test/truncate.t create mode 100644 test/unlink.t create mode 100644 test/utime.t create mode 100644 test/write.t diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..5d5c2dc --- /dev/null +++ b/.cvsignore @@ -0,0 +1 @@ +Fuse.bs Fuse.c Makefile blib pm_to_blib diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..d0b568c --- /dev/null +++ b/AUTHORS @@ -0,0 +1,4 @@ +Perl bindings +------------- + +Mark Glines diff --git a/Changes b/Changes new file mode 100644 index 0000000..56b4733 --- /dev/null +++ b/Changes @@ -0,0 +1,12 @@ +Revision history for Perl extension Fuse. + +0.01 Wed Nov 28 21:45:20 2001 + - original version; created by h2xs 1.21 with options + include/fuse.h + +0.02 Sun Dec 2 18:59:56 2001 + - works well enough to release, but still needs testing + +0.03 Wed Dec 5 02:17:52 2001 + - changed getattr() to smell like perl's stat() + - fleshed out the documentation a bit diff --git a/Fuse.pm b/Fuse.pm new file mode 100644 index 0000000..6a01677 --- /dev/null +++ b/Fuse.pm @@ -0,0 +1,360 @@ +package Fuse; + +use 5.006; +use strict; +use warnings; +use Errno; +use Carp; + +require Exporter; +require DynaLoader; +use AutoLoader; +use Data::Dumper; +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_DEBUG +) ] ); + +our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); + +our @EXPORT = qw( + FUSE_DEBUG +); +our $VERSION = '0.01'; + +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; + +sub main { + my (@subs) = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); + my (@names) = qw(getattr readlink getdir mknod mkdir unlink rmdir symlink + rename link chmod chown truncate utime open read write statfs); + my ($tmp) = 0; + my (%mapping) = map { $_ => $tmp++ } (@names); + my (%otherargs) = (debug=>0, mountpoint=>""); + while(my $name = shift) { + my ($subref) = shift; + if(exists($otherargs{$name})) { + $otherargs{$name} = $subref; + } else { + croak "There is no function $name" unless exists($mapping{$name}); + croak "Usage: Fuse::main(getattr => &my_getattr, ...)" unless $subref; + croak "Usage: Fuse::main(getattr => &my_getattr, ...)" unless ref($subref); + croak "Usage: Fuse::main(getattr => &my_getattr, ...)" unless ref($subref) eq "CODE"; + $subs[$mapping{$name}] = $subref; + } + } + perl_fuse_main($otherargs{debug},$otherargs{mountpoint},@subs); +} + +# 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=>\&my_getattr, getdir=>\&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. + +NOTE: I have only tested the things implemented in example.pl! +It should work, but some things may not. + +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 EXPORT + +None by default. + +=head2 EXPORTABLE CONSTANTS + +None. + +=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: + + +debug => boolean + +=over 1 + +This turns FUSE call tracing on and off. Default is 0 (which means off). + +=back + +mountpoint => string + +=over 1 + +The point at which to mount this filesystem. There is no default, you must +specify this. An example would be '/mnt'. + +=back + +unthreaded => boolean + +=over 1 + +This turns FUSE multithreading off and on. NOTE: This perlmodule does not +currently work properly in multithreaded mode! The author is unfortunately +not familiar enough with perl-threads internals, and according to the +documentation available at time of writing (2002-03-08), those internals are +subject to changing anyway. Note that singlethreaded mode also means that +you will not have to worry about reentrancy, though you will have to worry +about recursive lookups (since the kernel holds a global lock on your +filesystem and blocks waiting for one callback to complete before calling +another). + +I hope to add full multithreading functionality later, but for now, I +recommend you leave this option at the default, 1 (which means +unthreaded, no threads will be used and no reentrancy is needed). + +=back + +=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.) + +=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. Its 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). +Returns an errno. + +No creation, or trunctation flags (O_CREAT, O_EXCL, O_TRUNC) will be passed to open(). +Your open() method needs only check if the operation is permitted for the given flags, and return 0 for success. + +=head3 read + +Arguments: Pathname, numeric requestedsize, numeric offset. +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. You can use length($buffer) to +find the buffersize. +Returns an errno. + +Called in an attempt to write (or overwrite) a portion of the file. Be prepared because $buffer could contain random binary data with NULLs 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 + +=head1 AUTHOR + +Mark Glines, Emark@glines.orgE + +=head1 SEE ALSO + +L, the FUSE documentation. + +=cut diff --git a/Fuse.xs b/Fuse.xs new file mode 100644 index 0000000..233139a --- /dev/null +++ b/Fuse.xs @@ -0,0 +1,572 @@ +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" + +#include + +#undef DEBUGf +#if 0 +#define DEBUGf(f, a...) fprintf(stderr, "%s:%d (%i): " f,__BASE_FILE__,__LINE__,PL_stack_sp-PL_stack_base ,##a ) +#else +#define DEBUGf(a...) +#endif + +SV *_PLfuse_callbacks[18]; + +int _PLfuse_getattr(const char *file, struct stat *result) { + dSP; + int rv, statcount; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,strlen(file)))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[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_blksize = POPi; + result->st_ctime = POPi; + result->st_mtime = POPi; + result->st_atime = POPi; + /* What the HELL? Perl says the blockcount is the last argument. + * Everything else says the blockcount is the last argument. So why + * was it folded into the middle of the list? */ + result->st_blocks = POPi; + result->st_size = POPi; + 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; + return rv; +} + +int _PLfuse_readlink(const char *file,char *buf,size_t buflen) { + int rv; + char *rvstr; + dSP; + I32 ax; + if(buflen < 1) + return EINVAL; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[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; + return rv; +} + +int _PLfuse_getdir(const char *file, fuse_dirh_t dirh, fuse_dirfil_t dirfil) { + int prv, rv; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + PUTBACK; + prv = call_sv(_PLfuse_callbacks[2],G_ARRAY); + SPAGAIN; + if(prv) { + rv = POPi; + while(--prv) + dirfil(dirh,POPp,0); + } else { + fprintf(stderr,"getdir() handler returned nothing!\n"); + rv = -ENOSYS; + } + FREETMPS; + LEAVE; + PUTBACK; + return rv; +} + +int _PLfuse_mknod (const char *file, mode_t mode, dev_t dev) { + int rv; + SV *rvsv; + char *rvstr; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + XPUSHs(sv_2mortal(newSViv(mode))); + XPUSHs(sv_2mortal(newSViv(dev))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[3],G_SCALAR); + SPAGAIN; + if(rv) + rv = POPi; + else + rv = 0; + FREETMPS; + LEAVE; + PUTBACK; + return rv; +} + +int _PLfuse_mkdir (const char *file, mode_t mode) { + int rv; + SV *rvsv; + char *rvstr; + dSP; + DEBUGf("mkdir begin: %i\n",sp-PL_stack_base); + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + XPUSHs(sv_2mortal(newSViv(mode))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[4],G_SCALAR); + SPAGAIN; + if(rv) + rv = POPi; + else + rv = 0; + FREETMPS; + LEAVE; + PUTBACK; + DEBUGf("mkdir end: %i %i\n",sp-PL_stack_base,rv); + return rv; +} + + +int _PLfuse_unlink (const char *file) { + int rv; + SV *rvsv; + char *rvstr; + dSP; + DEBUGf("unlink begin: %i\n",sp-PL_stack_base); + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[5],G_SCALAR); + SPAGAIN; + if(rv) + rv = POPi; + else + rv = 0; + FREETMPS; + LEAVE; + PUTBACK; + DEBUGf("unlink end: %i\n",sp-PL_stack_base); + return rv; +} + +int _PLfuse_rmdir (const char *file) { + int rv; + SV *rvsv; + char *rvstr; + dSP; + DEBUGf("rmdir begin: %i\n",sp-PL_stack_base); + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[6],G_SCALAR); + SPAGAIN; + if(rv) + rv = POPi; + else + rv = 0; + FREETMPS; + LEAVE; + PUTBACK; + DEBUGf("rmdir end: %i %i\n",sp-PL_stack_base,rv); + return rv; +} + +int _PLfuse_symlink (const char *file, const char *new) { + int rv; + SV *rvsv; + char *rvstr; + dSP; + DEBUGf("symlink begin: %i\n",sp-PL_stack_base); + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + XPUSHs(sv_2mortal(newSVpv(new,0))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[7],G_SCALAR); + SPAGAIN; + if(rv) + rv = POPi; + else + rv = 0; + FREETMPS; + LEAVE; + PUTBACK; + DEBUGf("symlink end: %i\n",sp-PL_stack_base); + return rv; +} + +int _PLfuse_rename (const char *file, const char *new) { + int rv; + SV *rvsv; + char *rvstr; + dSP; + DEBUGf("rename begin: %i\n",sp-PL_stack_base); + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + XPUSHs(sv_2mortal(newSVpv(new,0))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[8],G_SCALAR); + SPAGAIN; + if(rv) + rv = POPi; + else + rv = 0; + FREETMPS; + LEAVE; + PUTBACK; + DEBUGf("rename end: %i\n",sp-PL_stack_base); + return rv; +} + +int _PLfuse_link (const char *file, const char *new) { + int rv; + SV *rvsv; + char *rvstr; + dSP; + DEBUGf("link begin: %i\n",sp-PL_stack_base); + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + XPUSHs(sv_2mortal(newSVpv(new,0))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[9],G_SCALAR); + SPAGAIN; + if(rv) + rv = POPi; + else + rv = 0; + FREETMPS; + LEAVE; + PUTBACK; + DEBUGf("link end: %i\n",sp-PL_stack_base); + return rv; +} + +int _PLfuse_chmod (const char *file, mode_t mode) { + int rv; + SV *rvsv; + char *rvstr; + dSP; + DEBUGf("chmod begin: %i\n",sp-PL_stack_base); + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + XPUSHs(sv_2mortal(newSViv(mode))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[10],G_SCALAR); + SPAGAIN; + if(rv) + rv = POPi; + else + rv = 0; + FREETMPS; + LEAVE; + PUTBACK; + DEBUGf("chmod end: %i\n",sp-PL_stack_base); + return rv; +} + +int _PLfuse_chown (const char *file, uid_t uid, gid_t gid) { + int rv; + SV *rvsv; + char *rvstr; + dSP; + DEBUGf("chown begin: %i\n",sp-PL_stack_base); + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + XPUSHs(sv_2mortal(newSViv(uid))); + XPUSHs(sv_2mortal(newSViv(gid))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[11],G_SCALAR); + SPAGAIN; + if(rv) + rv = POPi; + else + rv = 0; + FREETMPS; + LEAVE; + PUTBACK; + DEBUGf("chown end: %i\n",sp-PL_stack_base); + return rv; +} + +int _PLfuse_truncate (const char *file, off_t off) { + int rv; + SV *rvsv; + char *rvstr; + dSP; + DEBUGf("truncate begin: %i\n",sp-PL_stack_base); + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + XPUSHs(sv_2mortal(newSViv(off))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[12],G_SCALAR); + SPAGAIN; + if(rv) + rv = POPi; + else + rv = 0; + FREETMPS; + LEAVE; + PUTBACK; + DEBUGf("truncate end: %i\n",sp-PL_stack_base); + return rv; +} + +int _PLfuse_utime (const char *file, struct utimbuf *uti) { + int rv; + SV *rvsv; + char *rvstr; + dSP; + DEBUGf("utime begin: %i\n",sp-PL_stack_base); + 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(_PLfuse_callbacks[13],G_SCALAR); + SPAGAIN; + if(rv) + rv = POPi; + else + rv = 0; + FREETMPS; + LEAVE; + PUTBACK; + DEBUGf("utime end: %i\n",sp-PL_stack_base); + return rv; +} + +int _PLfuse_open (const char *file, int flags) { + int rv; + SV *rvsv; + char *rvstr; + dSP; + DEBUGf("open begin: %i\n",sp-PL_stack_base); + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + XPUSHs(sv_2mortal(newSViv(flags))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[14],G_SCALAR); + SPAGAIN; + if(rv) + rv = POPi; + else + rv = 0; + FREETMPS; + LEAVE; + PUTBACK; + DEBUGf("open end: %i %i\n",sp-PL_stack_base,rv); + return rv; +} + +int _PLfuse_read (const char *file, char *buf, size_t buflen, off_t off) { + int rv; + char *rvstr; + dSP; + DEBUGf("read begin: %i\n",sp-PL_stack_base); + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + XPUSHs(sv_2mortal(newSViv(buflen))); + XPUSHs(sv_2mortal(newSViv(off))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[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 %i\n",sp-PL_stack_base,rv); + return rv; +} + +int _PLfuse_write (const char *file, const char *buf, size_t buflen, off_t off) { + int rv; + char *rvstr; + dSP; + DEBUGf("write begin: %i\n",sp-PL_stack_base); + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(file,0))); + XPUSHs(sv_2mortal(newSVpvn(buf,buflen))); + XPUSHs(sv_2mortal(newSViv(off))); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[16],G_SCALAR); + SPAGAIN; + if(rv) + rv = POPi; + else + rv = 0; + FREETMPS; + LEAVE; + PUTBACK; + DEBUGf("write end: %i\n",sp-PL_stack_base); + return rv; +} + +int _PLfuse_statfs (const char *file, struct statfs *st) { + int rv; + char *rvstr; + dSP; + DEBUGf("statfs begin: %i\n",sp-PL_stack_base); + ENTER; + SAVETMPS; + PUSHMARK(SP); + PUTBACK; + rv = call_sv(_PLfuse_callbacks[17],G_ARRAY); + SPAGAIN; + if(rv > 5) { + st->f_bsize = POPi; + st->f_bfree = POPi; + st->f_blocks = POPi; + st->f_ffree = POPi; + st->f_files = POPi; + st->f_namelen = POPi; + if(rv > 6) + 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",sp-PL_stack_base); + return rv; +} + +struct fuse_operations _available_ops = { +getattr: _PLfuse_getattr, + _PLfuse_readlink, + _PLfuse_getdir, + _PLfuse_mknod, + _PLfuse_mkdir, + _PLfuse_unlink, + _PLfuse_rmdir, + _PLfuse_symlink, + _PLfuse_rename, + _PLfuse_link, + _PLfuse_chmod, + _PLfuse_chown, + _PLfuse_truncate, + _PLfuse_utime, + _PLfuse_open, + _PLfuse_read, + _PLfuse_write, + _PLfuse_statfs +}; + +MODULE = Fuse PACKAGE = Fuse +PROTOTYPES: DISABLE + +void +perl_fuse_main(...) + PREINIT: + struct fuse_operations fops = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}; + int i, fd, varnum = 0, debug, have_mnt; + char *mountpoint; + STRLEN n_a; + STRLEN l; + INIT: + if(items != 20) { + fprintf(stderr,"Perl<->C inconsistency or internal error\n"); + XSRETURN_UNDEF; + } + CODE: + debug = SvIV(ST(0)); + mountpoint = SvPV_nolen(ST(1)); + /* FIXME: reevaluate multithreading support when perl6 arrives */ + for(i=0;i<18;i++) { + SV *var = ST(i+2); + if((var != &PL_sv_undef) && SvROK(var)) { + if(SvTYPE(SvRV(var)) == SVt_PVCV) { + void **tmp1 = (void**)&_available_ops, **tmp2 = (void**)&fops; + tmp2[i] = tmp1[i]; + _PLfuse_callbacks[i] = var; + } else + croak("arg is not a code reference!"); + } + } + /* FIXME: need to pass fusermount arguments */ + fd = fuse_mount(mountpoint,NULL); + if(fd < 0) + croak("could not mount fuse filesystem!"); + fuse_loop(fuse_new(fd,debug ? "debug" : NULL,&fops)); diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..3012c02 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,7 @@ +Changes +Fuse.pm +Fuse.xs +Makefile.PL +MANIFEST +README +test.pl diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..6e66f46 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,17 @@ +use ExtUtils::MakeMaker; +# See lib/ExtUtils/MakeMaker.pm for details of how to influence +# the contents of the Makefile that is written. +WriteMakefile( + 'NAME' => 'Fuse', + 'VERSION_FROM' => 'Fuse.pm', # finds $VERSION + 'PREREQ_PM' => {}, # e.g., Module::Name => 1.1 + ($] >= 5.005 ? ## Add these new keywords supported since 5.005 + (ABSTRACT_FROM => 'Fuse.pm', # retrieve abstract from module + AUTHOR => 'Mark Glines ') : ()), + 'LIBS' => [''], # e.g., '-lm' + 'DEFINE' => '-g -ggdb', # e.g., '-DHAVE_SOMETHING' + # Insert -I. if you add *.h files later: + 'INC' => '-I../include', # e.g., '-I/usr/include/other' + # Un-comment this if you add C files to link with later: + 'OBJECT' => 'Fuse.o ../lib/.libs/libfuse.a -lpthread', # link all the C files too +); diff --git a/README b/README new file mode 100644 index 0000000..fb49cd7 --- /dev/null +++ b/README @@ -0,0 +1,69 @@ +Fuse version 0.03 +================= + +This is a test 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 userspace library and the FUSE kernel module. + + +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 + +I've begun to build a formal testing framework. Currently it can mount +and unmount loopback.pl, and all of the base-level functions have test +scripts. These need to be fleshed out as problems are noticed. + +The current test framework seems to work well, but the underlying mount/ +unmount infrastructure is a crock. I am not pleased with that code. + +While most things work, I do still have a TODO list: +* "du -sb" reports a couple orders of magnitude too large a size. +* need to sort out cleaner mount semantics for the test framework +* figure out how to un-linuxcentrify the statfs tests +* test everything on other architectures and OS's + diff --git a/examples/example.pl b/examples/example.pl new file mode 100644 index 0000000..9ba1117 --- /dev/null +++ b/examples/example.pl @@ -0,0 +1,90 @@ +#!/usr/bin/perl + +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=>\&e_getattr, + getdir=>\&e_getdir, + open=>\&e_open, + statfs=>\&e_statfs, + read=>\&e_read, + #debug=>1, threaded=>0 +); diff --git a/examples/loopback.pl b/examples/loopback.pl new file mode 100644 index 0000000..bdc8c22 --- /dev/null +++ b/examples/loopback.pl @@ -0,0 +1,136 @@ +#!/usr/bin/perl + +use strict; +use Fuse; +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); +require 'syscall.ph'; # for SYS_mknod and SYS_lchown + +sub fixup { return "/tmp/fusetest" . 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} +my ($mountpoint) = ""; +$mountpoint = shift(@ARGV) if @ARGV; +Fuse::main( + mountpoint=>$mountpoint, + getattr=>\&x_getattr, + readlink=>\&x_readlink, + getdir=>\&x_getdir, + mknod=>\&x_mknod, + mkdir=>\&x_mkdir, + unlink=>\&x_unlink, + rmdir=>\&x_rmdir, + symlink=>\&x_symlink, + rename=>\&x_rename, + link=>\&x_link, + chmod=>\&x_chmod, + chown=>\&x_chown, + truncate=>\&x_truncate, + utime=>\&x_utime, + open=>\&x_open, + read=>\&x_read, + write=>\&x_write, + statfs=>\&x_statfs, +); diff --git a/examples/rmount.pl b/examples/rmount.pl new file mode 100644 index 0000000..9ae1cc1 --- /dev/null +++ b/examples/rmount.pl @@ -0,0 +1,82 @@ +#!/usr/bin/perl + +use strict; +use Net::SSH 'sshopen2'; +use IPC::Open2; +use Fuse; +use Data::Dumper; + +my ($host, $dir, $mount) = @ARGV; +if(!defined($mount)) { + $mount = $dir; + if($host =~ /^(.*):(.*)$/) { + ($host,$dir) = ($1,$2); + } else { + die "usage: $0 user\@host remotedir mountpoint\n". + "or : $0 user\@host:remotedir mountpoint\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 { + 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 ($sig) = $SIG{ALRM}; + my ($VAR1); + $VAR1 = undef; + eval { + $SIG{ALRM} = sub { die "timeout\n" }; + alarm 10; + print WRITER $str; + my ($len, $data); + if(read(READER,$len,9) == 9) { + read(READER,$data,$len-length($data),length($data)) + while(length($data) < $len); + eval $data; + } + }; + alarm 0; + $SIG{ALRM} = $sig; + 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); diff --git a/examples/rmount_remote.pl b/examples/rmount_remote.pl new file mode 100644 index 0000000..e9e0866 --- /dev/null +++ b/examples/rmount_remote.pl @@ -0,0 +1,143 @@ +#!/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; +} diff --git a/test.pl b/test.pl new file mode 100644 index 0000000..e8152fd --- /dev/null +++ b/test.pl @@ -0,0 +1,8 @@ +#!/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"); diff --git a/test/chmod.t b/test/chmod.t new file mode 100644 index 0000000..366f89b --- /dev/null +++ b/test/chmod.t @@ -0,0 +1,11 @@ +#!/usr/bin/perl +use test::helper qw($_real $_point); +use Test::More; +plan tests => 4; +chdir($_point); +system("echo frog >file"); +ok(chmod(0644,"file"),"set unexecutable"); +ok(!-x "file","unexecutable"); +ok(chmod(0755,"file"),"set executable"); +ok(-x "file","executable"); +unlink("file"); diff --git a/test/chown.t b/test/chown.t new file mode 100644 index 0000000..8ccbb88 --- /dev/null +++ b/test/chown.t @@ -0,0 +1,14 @@ +#!/usr/bin/perl +use test::helper qw($_real $_point); +use Test::More; +plan tests => 4; +my (@stat); +chdir($_point); +system("echo frog >file"); +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"); diff --git a/test/getattr.t b/test/getattr.t new file mode 100644 index 0000000..4203275 --- /dev/null +++ b/test/getattr.t @@ -0,0 +1,42 @@ +#!/usr/bin/perl +use test::helper qw($_real $_point); +use Test::More; +use Data::Dumper; +plan tests => 28; +my ($a, $b) = ("$_real/wibble","$_point/wibble"); +`touch $b`; +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 and inode can legally change +shift(@astat); shift(@astat); +shift(@bstat); shift(@bstat); +is(join(" ",@astat),join(" ",@bstat),"stat()"); +`rm -f $a`; diff --git a/test/getdir.t b/test/getdir.t new file mode 100644 index 0000000..1d60561 --- /dev/null +++ b/test/getdir.t @@ -0,0 +1,33 @@ +#!/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 +map { system("touch \"$_\"") } @names; + +# 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; diff --git a/test/helper.pm b/test/helper.pm new file mode 100644 index 0000000..cd2bd55 --- /dev/null +++ b/test/helper.pm @@ -0,0 +1,23 @@ +#!/usr/bin/perl +package test::helper; +use strict; +use Exporter; +our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS); +@ISA = "Exporter"; +@EXPORT_OK = qw($_loop $_point $_pidfile $_real); +our($_loop, $_point, $_pidfile, $_real) = ("examples/loopback.pl","/mnt","test/s/mounted.pid","/tmp/fusetest"); +if($0 !~ qr|s/u?mount\.t$|) { + my ($reject) = 1; + if(-f $_pidfile) { + unless(system("ps `cat $_pidfile` | grep \"$_loop $_point\" >/dev/null")>>8) { + if(`mount | grep "on $_point"`) { + $reject = 0; + } else { + system("kill `cat $_pidfile`"); + } + } + } + $reject = 1 if (system("ls $_point >&/dev/null") >> 8); + die "not properly mounted\n" if $reject; +} +1; diff --git a/test/link.t b/test/link.t new file mode 100644 index 0000000..391b2f0 --- /dev/null +++ b/test/link.t @@ -0,0 +1,16 @@ +#!/usr/bin/perl +use test::helper qw($_real $_point); +use Test::More; +plan tests => 8; +chdir($_point); +system("echo hippity >womble"); +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"); diff --git a/test/mkdir.t b/test/mkdir.t new file mode 100644 index 0000000..90ec6f3 --- /dev/null +++ b/test/mkdir.t @@ -0,0 +1,11 @@ +#!/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"); diff --git a/test/mknod.t b/test/mknod.t new file mode 100644 index 0000000..35c5c82 --- /dev/null +++ b/test/mknod.t @@ -0,0 +1,37 @@ +#!/usr/bin/perl +use test::helper qw($_real $_point); +use Test::More; +plan tests => 24; +my (@stat); +chdir($_point); +ok(!(system("touch reg" )>>8),"create normal file"); +ok(!(system("mknod chr c 2 3")>>8),"create chrdev"); +ok(!(system("mknod blk b 2 3")>>8),"create blkdev"); +ok(!(system("mknod fifo p" )>>8),"create fifo"); +chdir($_real); +ok(-e "reg" ,"normal file exists"); +ok(-e "chr" ,"chrdev exists"); +ok(-e "blk" ,"blkdev exists"); +ok(-e "fifo","fifo exists"); +ok(-f "reg" ,"normal file is normal file"); +ok(-c "chr" ,"chrdev is chrdev"); +ok(-b "blk" ,"blkdev is blkdev"); +ok(-p "fifo","fifo is fifo"); +@stat = stat("chr"); +is($stat[6],3+(2<<8),"chrdev has right major,minor"); +@stat = stat("blk"); +is($stat[6],3+(2<<8),"blkdev has right major,minor"); +chdir($_point); +ok(-e "reg" ,"normal file exists"); +ok(-e "chr" ,"chrdev exists"); +ok(-e "blk" ,"blkdev exists"); +ok(-e "fifo","fifo exists"); +ok(-f "reg" ,"normal file is normal file"); +ok(-c "chr" ,"chrdev is chrdev"); +ok(-b "blk" ,"blkdev is blkdev"); +ok(-p "fifo","fifo is fifo"); +@stat = stat("chr"); +is($stat[6],3+(2<<8),"chrdev has right major,minor"); +@stat = stat("blk"); +is($stat[6],3+(2<<8),"blkdev has right major,minor"); +map { unlink } qw(reg chr blk fifo); diff --git a/test/open.t b/test/open.t new file mode 100644 index 0000000..030dc1f --- /dev/null +++ b/test/open.t @@ -0,0 +1,10 @@ +#!/usr/bin/perl +use test::helper qw($_real $_point); +use Test::More; +plan tests => 1; +chdir($_real); +system("echo frog >file"); +chdir($_point); +ok(open(FILE,"file"),"open"); +close(FILE); +unlink("file"); diff --git a/test/read.t b/test/read.t new file mode 100644 index 0000000..5eca920 --- /dev/null +++ b/test/read.t @@ -0,0 +1,13 @@ +#!/usr/bin/perl +use test::helper qw($_real $_point); +use Test::More; +plan tests => 3; +chdir($_real); +system("echo frog >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"); diff --git a/test/readlink.t b/test/readlink.t new file mode 100644 index 0000000..85b9ffc --- /dev/null +++ b/test/readlink.t @@ -0,0 +1,11 @@ +#!/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"); diff --git a/test/rename.t b/test/rename.t new file mode 100644 index 0000000..9fbb330 --- /dev/null +++ b/test/rename.t @@ -0,0 +1,12 @@ +#!/usr/bin/perl +use test::helper qw($_real $_point); +use Test::More; +plan tests => 5; +chdir($_point); +system("echo hippity >frog"); +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"); diff --git a/test/rmdir.t b/test/rmdir.t new file mode 100644 index 0000000..36f0378 --- /dev/null +++ b/test/rmdir.t @@ -0,0 +1,13 @@ +#!/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"); diff --git a/test/s/mount.t b/test/s/mount.t new file mode 100644 index 0000000..26f6fc2 --- /dev/null +++ b/test/s/mount.t @@ -0,0 +1,25 @@ +#!/usr/bin/perl -w +use test::helper qw($_point $_loop $_real $_pidfile); +use strict; +use Test::More tests => 3; +ok(!(scalar grep(/ on $_point /,`cat /proc/mounts`)),"already mounted"); +ok(-f $_loop,"loopback exists"); + +if(!fork()) { + #close(STDIN); + close(STDOUT); + close(STDERR); + `echo $$ >test/s/mounted.pid`; + exec("perl $_loop $_point"); + exit(1); +} +select(undef, undef, undef, 0.5); +my ($success) = `cat /proc/mounts` =~ / $_point /; +ok($success,"mount succeeded"); +system("rm -rf $_real"); +unless($success) { + kill('INT',`cat $_pidfile`); + unlink($_pidfile); +} else { + mkdir($_real); +} diff --git a/test/s/umount.t b/test/s/umount.t new file mode 100644 index 0000000..da60677 --- /dev/null +++ b/test/s/umount.t @@ -0,0 +1,7 @@ +#!/usr/bin/perl +use test::helper qw($_point $_real $_pidfile); +use strict; +use Test::More tests => 1; +system("umount $_point"); +ok(1,"unmount"); +system("rm -rf $_real $_pidfile"); diff --git a/test/statfs.t b/test/statfs.t new file mode 100644 index 0000000..fb94704 --- /dev/null +++ b/test/statfs.t @@ -0,0 +1,21 @@ +#!/usr/bin/perl +use test::helper qw($_real $_point); +use Test::More; +require 'syscall.ph'; # for SYS_statfs +plan tests => 7; +my ($statfs_data) = " " x 10; +my ($tmp) = $_point; +ok(!syscall(&SYS_statfs,$tmp,$statfs_data),"statfs"); +# FIXME: this is soooooo linux-centric. perhaps parse the output of /bin/df? +my @list = unpack("LSSL8",$statfs_data); +shift(@list); +is(shift(@list),4096,"block size"); +shift(@list); +is(shift(@list),1000000,"blocks"); +is(shift(@list),500000,"blocks free"); +shift(@list); +is(shift(@list),1000000,"files"); +is(shift(@list),500000,"files free"); +shift(@list); +shift(@list); +is(shift(@list),255,"namelen"); diff --git a/test/symlink.t b/test/symlink.t new file mode 100644 index 0000000..19cc72d --- /dev/null +++ b/test/symlink.t @@ -0,0 +1,19 @@ +#!/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"); +system("cd dira; touch filea; ln -s filea fileb"); +is(system("cp -a dira dirb")>>8,0,"cp -a"); +system("rm -rf dira dirb"); diff --git a/test/test-template b/test/test-template new file mode 100644 index 0000000..ef57e08 --- /dev/null +++ b/test/test-template @@ -0,0 +1,5 @@ +#!/usr/bin/perl +use test::helper qw($_real $_point); +use Test::More; +plan tests => 1; +ok(1); diff --git a/test/truncate.t b/test/truncate.t new file mode 100644 index 0000000..8607421 --- /dev/null +++ b/test/truncate.t @@ -0,0 +1,12 @@ +#!/usr/bin/perl +use test::helper qw($_real $_point); +use Test::More; +plan tests => 5; +chdir($_point); +system("echo hippity >womble"); +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"); diff --git a/test/unlink.t b/test/unlink.t new file mode 100644 index 0000000..eef8c1a --- /dev/null +++ b/test/unlink.t @@ -0,0 +1,14 @@ +#!/usr/bin/perl +use test::helper qw($_real $_point); +use Test::More; +plan tests => 4; +chdir($_point); +system("touch 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"); diff --git a/test/utime.t b/test/utime.t new file mode 100644 index 0000000..8ccefc6 --- /dev/null +++ b/test/utime.t @@ -0,0 +1,13 @@ +#!/usr/bin/perl +use test::helper qw($_real $_point); +use Test::More; +plan tests => 3; +my (@stat); +chdir($_real); +system("echo frog >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"); diff --git a/test/write.t b/test/write.t new file mode 100644 index 0000000..58af2aa --- /dev/null +++ b/test/write.t @@ -0,0 +1,45 @@ +#!/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 +system("echo frogbing >writefile"); + +# 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"); -- 2.20.1