From 20dd496d09b6f698eba37c64c7f8ad9745db1c02 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Thu, 13 Dec 2001 10:55:07 +0000 Subject: [PATCH] fuser-perl changes --- perl/Changes | 4 ++ perl/Fuse.pm | 109 +++++++++++++++++++++++++++++++++++-------- perl/Fuse.xs | 89 ++++++++++++++++++++++++----------- perl/example.pl | 15 +++--- perl/loopback.pl | 117 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 281 insertions(+), 53 deletions(-) create mode 100644 perl/loopback.pl diff --git a/perl/Changes b/perl/Changes index 3981203..56b4733 100644 --- a/perl/Changes +++ b/perl/Changes @@ -6,3 +6,7 @@ Revision history for Perl extension Fuse. 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/perl/Fuse.pm b/perl/Fuse.pm index 2f4742a..93acfb6 100644 --- a/perl/Fuse.pm +++ b/perl/Fuse.pm @@ -99,116 +99,185 @@ Fuse - write filesystems in Perl using FUSE 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: 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. diff --git a/perl/Fuse.xs b/perl/Fuse.xs index 9770c3a..ca0459d 100644 --- a/perl/Fuse.xs +++ b/perl/Fuse.xs @@ -4,6 +4,12 @@ #include +#undef DEBUGf +#if 1 +#define DEBUGf(a...) fprintf(stderr, ##a) +#else +#define DEBUGf(a...) +#endif static int not_here(char *s) { @@ -30,33 +36,29 @@ int _PLfuse_getattr(const char *file, struct stat *result) { 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; @@ -67,6 +69,9 @@ int _PLfuse_readlink(const char *file,char *buf,size_t buflen) { int rv; char *rvstr; dXSARGS; + DEBUGf("readlink begin\n"); + if(buflen < 1) + return EINVAL; ENTER; SAVETMPS; PUSHMARK(SP); @@ -85,12 +90,15 @@ int _PLfuse_readlink(const char *file,char *buf,size_t buflen) { } 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); @@ -99,7 +107,7 @@ int _PLfuse_getdir(const char *file, fuse_dirh_t dirh, fuse_dirfil_t dirfil) { 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; @@ -114,6 +122,7 @@ int _PLfuse_getdir(const char *file, fuse_dirh_t dirh, fuse_dirfil_t dirfil) { } FREETMPS; LEAVE; + DEBUGf("getdir end\n"); return rv; } @@ -122,6 +131,7 @@ int _PLfuse_mknod (const char *file, mode_t mode, dev_t dev) { SV *rvsv; char *rvstr; dSP; + DEBUGf("mknod begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); @@ -137,6 +147,7 @@ int _PLfuse_mknod (const char *file, mode_t mode, dev_t dev) { rv = 0; FREETMPS; LEAVE; + DEBUGf("mknod end: %i\n",rv); return rv; } @@ -145,6 +156,7 @@ int _PLfuse_mkdir (const char *file, mode_t mode) { SV *rvsv; char *rvstr; dSP; + DEBUGf("mkdir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); @@ -159,6 +171,7 @@ int _PLfuse_mkdir (const char *file, mode_t mode) { rv = 0; FREETMPS; LEAVE; + DEBUGf("mkdir end\n"); return rv; } @@ -168,6 +181,7 @@ int _PLfuse_unlink (const char *file) { SV *rvsv; char *rvstr; dSP; + DEBUGf("unlink begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); @@ -181,6 +195,7 @@ int _PLfuse_unlink (const char *file) { rv = 0; FREETMPS; LEAVE; + DEBUGf("unlink end\n"); return rv; } @@ -189,6 +204,7 @@ int _PLfuse_rmdir (const char *file) { SV *rvsv; char *rvstr; dSP; + DEBUGf("rmdir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); @@ -202,6 +218,7 @@ int _PLfuse_rmdir (const char *file) { rv = 0; FREETMPS; LEAVE; + DEBUGf("rmdir end\n"); return rv; } @@ -210,6 +227,7 @@ int _PLfuse_symlink (const char *file, const char *new) { SV *rvsv; char *rvstr; dSP; + DEBUGf("symlink begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); @@ -224,6 +242,7 @@ int _PLfuse_symlink (const char *file, const char *new) { rv = 0; FREETMPS; LEAVE; + DEBUGf("symlink end\n"); return rv; } @@ -232,6 +251,7 @@ int _PLfuse_rename (const char *file, const char *new) { SV *rvsv; char *rvstr; dSP; + DEBUGf("rename begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); @@ -246,6 +266,7 @@ int _PLfuse_rename (const char *file, const char *new) { rv = 0; FREETMPS; LEAVE; + DEBUGf("rename end\n"); return rv; } @@ -254,6 +275,7 @@ int _PLfuse_link (const char *file, const char *new) { SV *rvsv; char *rvstr; dSP; + DEBUGf("link begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); @@ -268,6 +290,7 @@ int _PLfuse_link (const char *file, const char *new) { rv = 0; FREETMPS; LEAVE; + DEBUGf("link end\n"); return rv; } @@ -276,6 +299,7 @@ int _PLfuse_chmod (const char *file, mode_t mode) { SV *rvsv; char *rvstr; dSP; + DEBUGf("chmod begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); @@ -290,6 +314,7 @@ int _PLfuse_chmod (const char *file, mode_t mode) { rv = 0; FREETMPS; LEAVE; + DEBUGf("chmod end\n"); return rv; } @@ -298,6 +323,7 @@ int _PLfuse_chown (const char *file, uid_t uid, gid_t gid) { SV *rvsv; char *rvstr; dSP; + DEBUGf("chown begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); @@ -313,6 +339,7 @@ int _PLfuse_chown (const char *file, uid_t uid, gid_t gid) { rv = 0; FREETMPS; LEAVE; + DEBUGf("chown end\n"); return rv; } @@ -321,6 +348,7 @@ int _PLfuse_truncate (const char *file, off_t off) { SV *rvsv; char *rvstr; dSP; + DEBUGf("truncate begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); @@ -335,6 +363,7 @@ int _PLfuse_truncate (const char *file, off_t off) { rv = 0; FREETMPS; LEAVE; + DEBUGf("truncate end\n"); return rv; } @@ -343,6 +372,7 @@ int _PLfuse_utime (const char *file, struct utimbuf *uti) { SV *rvsv; char *rvstr; dSP; + DEBUGf("utime begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); @@ -358,6 +388,7 @@ int _PLfuse_utime (const char *file, struct utimbuf *uti) { rv = 0; FREETMPS; LEAVE; + DEBUGf("utime end\n"); return rv; } @@ -366,6 +397,7 @@ int _PLfuse_open (const char *file, int flags) { SV *rvsv; char *rvstr; dSP; + DEBUGf("open begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); @@ -380,6 +412,7 @@ int _PLfuse_open (const char *file, int flags) { rv = 0; FREETMPS; LEAVE; + DEBUGf("open end: %i\n",rv); return rv; } @@ -387,6 +420,7 @@ int _PLfuse_read (const char *file, char *buf, size_t buflen, off_t off) { int rv; char *rvstr; dXSARGS; + DEBUGf("read begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); @@ -403,13 +437,14 @@ int _PLfuse_read (const char *file, char *buf, size_t buflen, off_t off) { 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; } @@ -417,6 +452,7 @@ int _PLfuse_write (const char *file, const char *buf, size_t buflen, off_t off) int rv; char *rvstr; dSP; + DEBUGf("write begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); @@ -432,6 +468,7 @@ int _PLfuse_write (const char *file, const char *buf, size_t buflen, off_t off) rv = 0; FREETMPS; LEAVE; + DEBUGf("write end\n"); return rv; } diff --git a/perl/example.pl b/perl/example.pl index 257f2c9..bc62cad 100755 --- a/perl/example.pl +++ b/perl/example.pl @@ -37,13 +37,13 @@ sub e_getattr { 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 { @@ -79,5 +79,6 @@ Fuse::main( getattr=>\&e_getattr, getdir=>\&e_getdir, open=>\&e_open, - read=>\&e_read, + #read=>\&e_read, + #debug=>1, threaded=>0 ); diff --git a/perl/loopback.pl b/perl/loopback.pl new file mode 100644 index 0000000..2a8496a --- /dev/null +++ b/perl/loopback.pl @@ -0,0 +1,117 @@ +#!/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 +); -- 2.30.2