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
use Fuse;
my ($mountpoint) = "";
$mountpoint = shift(@ARGV) if @ARGV;
- Fuse::main(mountpoint=>$mountpoint,getattr=>\&my_getattr,getdir=>\&my_getdir, ...);
+ 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.
+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.
+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);".
+In the following definitions, "errno" can be 0 (for a success),
+-EINVAL, -ENOENT, -EONFIRE, any integer less than 1 really.
-=head2 FUNCTIONS
+You can import standard error constants by saying something like
+"use POSIX qw(EDOTDOT ENOANO);".
-=head3 getattr
-
-Arguments: filename.
-Returns a list, one of the following 4 possibilities:
-
-$errno or
+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.
-($blocks,$size,$gid,$uid,$nlink,$modes,$time) or
+=head2 FUNCTIONS YOUR FILESYSTEM MAY IMPLEMENT
-($errno,$blocks,$size,$gid,$uid,$nlink,$modes,$time) or
-
-($errno,$blksize,$blocks,$size,$gid,$uid,$nlink,$modes,$time)
+=head3 getattr
-B<FIXME>: device numeric, for filesystems that implement mknod?
+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 errno (usually 0).
+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).
+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.
+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.
+
=head2 EXPORT
None by default.
#include <fuse.h>
+#undef DEBUGf
+#if 1
+#define DEBUGf(a...) fprintf(stderr, ##a)
+#else
+#define DEBUGf(a...)
+#endif
static int
not_here(char *s)
{
PUTBACK;
rv = call_sv(_PLfuse_callbacks[0],G_ARRAY);
SPAGAIN;
- if(rv < 7) {
- if(rv > 1)
- croak("inappropriate number of returned values from getattr");
- if(rv)
+ 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 = -EINVAL;
+ rv = -ENOENT;
} else {
- if(rv > 9)
- croak("inappropriate number of returned values from getattr");
- result->st_ctime = result->st_mtime = result->st_atime = POPi;
- result->st_mode = POPi;
- result->st_nlink = POPi;
- result->st_uid = POPi;
- result->st_gid = POPi;
- result->st_size = POPi;
result->st_blocks = POPi;
- if(rv > 7) {
- if(rv > 8)
- result->st_blksize = POPi;
- else
- result->st_blksize = 1024;
- rv = POPi;
- } else {
- result->st_blksize = 1024;
- rv = 0;
- }
+ result->st_blksize = POPi;
+ result->st_ctime = POPi;
+ result->st_mtime = POPi;
+ result->st_atime = 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;
int rv;
char *rvstr;
dXSARGS;
+ DEBUGf("readlink begin\n");
+ if(buflen < 1)
+ return EINVAL;
ENTER;
SAVETMPS;
PUSHMARK(SP);
}
FREETMPS;
LEAVE;
+ buf[buflen-1] = 0;
+ DEBUGf("readlink end\n");
return rv;
}
int _PLfuse_getdir(const char *file, fuse_dirh_t dirh, fuse_dirfil_t dirfil) {
int prv, rv;
dXSARGS;
+ DEBUGf("getdir begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
prv = call_sv(_PLfuse_callbacks[2],G_ARRAY);
SPAGAIN;
if(prv) {
- SV *mysv = POPs;
+ SV *mysv = sv_2mortal(POPs);
if(!SvIOK(mysv)) {
fprintf(stderr,"last getdir retval needs to be numeric (e.g. 0 or -ENOENT) (%s)\n",SvPV_nolen(mysv));
rv = -ENOSYS;
}
FREETMPS;
LEAVE;
+ DEBUGf("getdir end\n");
return rv;
}
SV *rvsv;
char *rvstr;
dSP;
+ DEBUGf("mknod begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
rv = 0;
FREETMPS;
LEAVE;
+ DEBUGf("mknod end: %i\n",rv);
return rv;
}
SV *rvsv;
char *rvstr;
dSP;
+ DEBUGf("mkdir begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
rv = 0;
FREETMPS;
LEAVE;
+ DEBUGf("mkdir end\n");
return rv;
}
SV *rvsv;
char *rvstr;
dSP;
+ DEBUGf("unlink begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
rv = 0;
FREETMPS;
LEAVE;
+ DEBUGf("unlink end\n");
return rv;
}
SV *rvsv;
char *rvstr;
dSP;
+ DEBUGf("rmdir begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
rv = 0;
FREETMPS;
LEAVE;
+ DEBUGf("rmdir end\n");
return rv;
}
SV *rvsv;
char *rvstr;
dSP;
+ DEBUGf("symlink begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
rv = 0;
FREETMPS;
LEAVE;
+ DEBUGf("symlink end\n");
return rv;
}
SV *rvsv;
char *rvstr;
dSP;
+ DEBUGf("rename begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
rv = 0;
FREETMPS;
LEAVE;
+ DEBUGf("rename end\n");
return rv;
}
SV *rvsv;
char *rvstr;
dSP;
+ DEBUGf("link begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
rv = 0;
FREETMPS;
LEAVE;
+ DEBUGf("link end\n");
return rv;
}
SV *rvsv;
char *rvstr;
dSP;
+ DEBUGf("chmod begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
rv = 0;
FREETMPS;
LEAVE;
+ DEBUGf("chmod end\n");
return rv;
}
SV *rvsv;
char *rvstr;
dSP;
+ DEBUGf("chown begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
rv = 0;
FREETMPS;
LEAVE;
+ DEBUGf("chown end\n");
return rv;
}
SV *rvsv;
char *rvstr;
dSP;
+ DEBUGf("truncate begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
rv = 0;
FREETMPS;
LEAVE;
+ DEBUGf("truncate end\n");
return rv;
}
SV *rvsv;
char *rvstr;
dSP;
+ DEBUGf("utime begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
rv = 0;
FREETMPS;
LEAVE;
+ DEBUGf("utime end\n");
return rv;
}
SV *rvsv;
char *rvstr;
dSP;
+ DEBUGf("open begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
rv = 0;
FREETMPS;
LEAVE;
+ DEBUGf("open end: %i\n",rv);
return rv;
}
int rv;
char *rvstr;
dXSARGS;
+ DEBUGf("read begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
if(SvTYPE(mysv) == SVt_IV)
rv = SvIV(mysv);
else {
- rv = SvLEN(mysv);
+ rv = SvCUR(mysv);
if(rv > buflen)
- croak("read() handler returned more than buflen!");
+ croak("read() handler returned more than buflen! (%i > %i)",rv,buflen);
if(rv)
memcpy(buf,SvPV_nolen(mysv),rv);
}
}
+ DEBUGf("read end\n");
return rv;
}
int rv;
char *rvstr;
dSP;
+ DEBUGf("write begin\n");
ENTER;
SAVETMPS;
PUSHMARK(SP);
rv = 0;
FREETMPS;
LEAVE;
+ DEBUGf("write end\n");
return rv;
}
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 ($blocks, $gid, $uid, $nlink) = (1,0,0,1);
- # 4 possible return values:
+ 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
- return ($blocks,$size,$gid,$uid,$nlink,$modes,$files{$file}{ctime});
- # return ($errno,$blocks,$size,$gid,$uid,$nlink,$modes,$time);
- # return ($errno,$blksize,$blocks,$size,$gid,$uid,$nlink,$modes,$time);
- # if omitted, errno defaults to 0, and blksize defaults to 1024.
+ #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 {
getattr=>\&e_getattr,
getdir=>\&e_getdir,
open=>\&e_open,
- read=>\&e_read,
+ #read=>\&e_read,
+ #debug=>1, threaded=>0
);
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use Fuse;
+use POSIX qw(ENOENT ENOSYS EEXIST EPERM O_RDONLY O_RDWR O_APPEND O_CREAT);
+use Fcntl qw(S_ISBLK S_ISCHR S_ISFIFO);
+
+sub debug {
+ print(STDERR join(",",@_),"\n");
+}
+
+sub fixup { return "/tmp/test" . shift }
+
+sub x_getattr {
+ my ($file) = fixup(shift);
+ debug("getattr $file");
+ return -ENOENT() unless -e $file;
+ debug(stat($file));
+ return (stat($file));
+}
+
+sub x_getdir {
+ my ($dirname) = fixup(shift);
+ debug("getdir >$dirname<");
+ unless(opendir(DIRHANDLE,$dirname)) {
+ debug("ENOENT");
+ return -ENOENT();
+ }
+ debug("ok");
+ my (@files) = readdir(DIRHANDLE);
+ closedir(DIRHANDLE);
+ debug(@files);
+ return (@files, 0);
+}
+
+sub x_open {
+ my ($file) = fixup(shift);
+ debug("open flags = $_[0]");
+ my ($fd) = POSIX::open($file,@_);
+ if(!defined($fd)) {
+ debug("POSIX::open(".join(",",$file,@_).") returned undef");
+ return -ENOSYS();
+ }
+ debug("open $file = $fd");
+ return $fd if $fd < 0;
+ POSIX::close($fd);
+ debug("good: $fd");
+ return 0;
+}
+
+sub x_read {
+ my ($file,$bufsize,$off) = @_;
+ debug("read",@_);
+ my ($rv) = -ENOSYS();
+ return -ENOENT() unless -e ($file = fixup($file));
+ return -ENOSYS() unless sysopen(FILE,$file,O_RDONLY());
+ if(sysseek(FILE,$off,0)) { sysread(FILE,$rv,$bufsize); }
+ close(FILE);
+ debug("good");
+ return $rv;
+}
+
+sub x_write {
+ my ($file,$buf,$off) = @_;
+ debug("write",@_);
+ my ($rv);
+ return -ENOENT() unless -e ($file = fixup($file));
+ return -ENOSYS() unless sysopen(FILE,$file,O_RDWR()|O_APPEND()|O_CREAT());
+ if(sysseek(FILE,$off,0)) { $rv = syswrite(FILE,$buf); }
+ $rv = -ENOSYS() unless $rv;
+ close(FILE);
+ debug("good");
+ return $rv;
+}
+
+sub err { return (-shift || -$!) }
+
+sub x_readlink { return err(readlink(fixup(shift)) ); }
+sub x_unlink { return unlink(fixup(shift)) ? 0 : -$!; }
+sub x_rmdir { return err(rmdir(fixup(shift)) ); }
+sub x_symlink { return err(symlink(fixup(shift),fixup(shift))); }
+sub x_rename { return err(rename(fixup(shift),fixup(shift)) ); }
+sub x_link { return err(link(fixup(shift),fixup(shift)) ); }
+sub x_mkdir { return err(mkdir(fixup(shift),shift) ); }
+sub x_chmod { return err(chmod(fixup(shift),shift) ); }
+sub x_chown { return err(chown(fixup(shift),shift,shift) ); }
+sub x_chmod { return err(chmod(fixup(shift),shift) ); }
+sub x_truncate { return err(truncate(fixup(shift),shift) ); }
+sub x_utime { return utime($_[1],$_[2],fixup($_[0])) ? 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) = @_;
+ return -EEXIST() if -e ($file = fixup($file));
+ return -EPERM() if (system("touch $file 2>/dev/null") >> 8);
+ if(S_ISBLK($modes) || S_ISCHR($modes) || S_ISFIFO($modes)) {
+ system("rm -f $file 2>/dev/null");
+ my ($chr) = 'c';
+ my ($omodes) = sprintf("%o",$modes & 0x1ff);
+ $chr = 'b' if S_ISBLK($modes);
+ if(S_ISFIFO($modes)) {
+ $chr = 'p';
+ $dev = "";
+ } else {
+ $dev = (($dev>>8) & 255) . " " . ($dev & 255);
+ }
+ system("mknod --mode=$omodes '$file' $chr $dev");
+ }
+}
+
+my ($mountpoint) = "";
+$mountpoint = shift(@ARGV) if @ARGV;
+Fuse::main(mountpoint=>$mountpoint, getattr=>\&x_getattr, readlink=>\&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
+);