Addition of file handles on open files rt-57517-file-handles
authorJustin Fletcher <gerph@gerph.org>
Sun, 16 May 2010 16:25:07 +0000 (18:25 +0200)
committerDobrica Pavlinusic <dpavlin@rot13.org>
Sun, 16 May 2010 16:25:07 +0000 (18:25 +0200)
Sat May 15 16:36:47 2010: Request 57517 was acted upon.
Transaction: Ticket created by gerph
       Queue: Fuse
     Subject: Addition of file handles on open files
   Broken in: 0.09_3
    Severity: (no value)
       Owner: Nobody
  Requestors: gerph@gerph.org
      Status: new
 Ticket <URL: https://rt.cpan.org/Ticket/Display.html?id=57517 >

Hiya,

Whilst trying to write a filesystem using the Fuse module, I found it
surprising that there were no 'file handles' available when you opened
a file. The only way you can know what file was referenced is by its
name, which is not useful if your filesystem is intended to return
different results for each file opened. It turned out in my case to not
matter, but consider a FS which returned a different story every time
you opened it. Or, more practically, a FS which returned the contents
of the latest checked in file - and whilst you were operating on the
file the latest checked in file changed. There are ways around this -
files becoming invariant whilst it has any open instances of itself or
similar - but these are not ideal.

The way that Fuse appears to do this is that the filesystem updates a
property ('fh') in the fuse_file_info structure on open, to contain the
context for the opened file. I've put together a change which allows
you to return a second parameter from open containing this value, which
is then passed to all the functions which operate on open files (read,
write, flush, release). Because we're retaining a reference to the SV
that was returned (and is otherwise unused in the perl) we also
increment the refcount on open, and decrement it on release - I think
that's all that's necessary to prevent a leak, but I've never done any
XS work before this so I can't be certain.

I'd expect that under normal circumstances you'd open a file, set up a
hashref containing properties for the file that you've opened and use
'return (0, $handle);' to return it. If you don't return a handle, the
old behaviour remains - undef will be passed to the implementation in
place of the handle (which the implementation wasn't expecting so won't
care about).

In addition, I've also added the ability to set the 'direct_io',
'keepcache' and 'nonseekable' properties by changing a hashref which is
passed to the 'open' call.

The archive I've attached contains the Fuse.pm and Fuse.xs files in
their entirity, together with the diffs from 0.09_3. There is also a
rudimentary example FS, based on the example one in the Fuse
distribution, which shows the file handle to be working. It seems to be
working in my more complex MythTV filesystem that I'm still trying to
make useful.

I couldn't actually test the nonseekable property as the fuse I have
seems to be 2.4, so I don't have that property available to me here -
I've just followed what the documentation says should be available.

I've not added anything to the tests, because I'm not sure how to do
that really, but I hope that the change to add the file handles is
useful.

Fuse.pm [changed mode: 0644->0755]
Fuse.xs [changed mode: 0644->0755]
examples/example.pl

diff --git a/Fuse.pm b/Fuse.pm
old mode 100644 (file)
new mode 100755 (executable)
index 2132aa7..f9f6e73
--- a/Fuse.pm
+++ b/Fuse.pm
@@ -294,7 +294,7 @@ example rv: return "/proc/self/fd/stdin";
 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.
+This is used to obtain directory listings.  It's opendir(), readdir(), filldir() and closedir() all in one call.
 
 example rv: return ('.', 'a', 'b', 0);
 
@@ -379,26 +379,31 @@ 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.
+and O_SYNC, constants you can import from POSIX), fileinfo hash reference.
+Returns an errno, a file handle (optional).
 
 No creation, or trunctation 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.9 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 requestedsize, numeric offset.
+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.  You can use length($buffer) to
+Arguments:  Pathname, scalar buffer, numeric offset, file handle.  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.
+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
 
@@ -417,7 +422,7 @@ or
 
 =head3 flush
 
-Arguments: Pathname
+Arguments: Pathname, file handle
 Returns an errno or 0 on success.
 
 Called to synchronise any cached data. This is called before the file
@@ -425,7 +430,7 @@ is closed. It may be called multiple times before a file is closed.
 
 =head3 release
 
-Arguments: Pathname, numeric flags passed to open
+Arguments: Pathname, numeric flags passed to open, file handle
 Returns an errno or 0 on success.
 
 Called to indicate that there are no more references to the file. Called once
diff --git a/Fuse.xs b/Fuse.xs
old mode 100644 (file)
new mode 100755 (executable)
index b87d44a..a5cc6c7
--- a/Fuse.xs
+++ b/Fuse.xs
@@ -418,6 +418,7 @@ int _PLfuse_utime (const char *file, struct utimbuf *uti) {
 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;
@@ -425,13 +426,68 @@ int _PLfuse_open (const char *file, struct fuse_file_info *fi) {
        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();
+#if FUSE_VERSION >= 24
+       hv_store(fihash, "direct_io", 9, newSViv(fi->direct_io), 0);
+       hv_store(fihash, "keep_cache", 10, newSViv(fi->keep_cache), 0);
+#endif
+#if FUSE_VERSION >= 29
+       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(_PLfuse_callbacks[14],G_SCALAR);
+       /* Open called with filename, flags */
+       rv = call_sv(_PLfuse_callbacks[14],G_ARRAY);
        SPAGAIN;
        if(rv)
+       {
+               SV *sv;
+               if (rv > 1)
+               {
+                       sv = POPs;
+                       if (SvOK(sv))
+                       {
+                               /* We're holding on to the sv reference until
+                                * after exit of this function, so we need to
+                                * increment its reference count
+                                */
+                               fi->fh = SvREFCNT_inc(sv);
+                       }
+               }
                rv = POPi;
+       }
        else
                rv = 0;
+       if (rv == 0)
+       {
+               /* Success, so copy the file handle which they returned */
+#if FUSE_VERSION >= 24
+               SV **svp;
+               svp = hv_fetch(fihash, "direct_io", 9, 0);
+               if (svp != NULL)
+               {
+                       fi->direct_io = SvIV(*svp);
+               }
+               svp = hv_fetch(fihash, "keep_cache", 10, 0);
+               if (svp != NULL)
+               {
+                       fi->keep_cache = SvIV(*svp);
+               }
+#endif
+#if FUSE_VERSION >= 29
+               svp = hv_fetch(fihash, "nonseekable", 11, 0);
+               if (svp != NULL)
+               {
+                       fi->nonseekable = SvIV(*svp);
+               }
+#endif
+       }
        FREETMPS;
        LEAVE;
        PUTBACK;
@@ -450,6 +506,7 @@ int _PLfuse_read (const char *file, char *buf, size_t buflen, off_t off, struct
        XPUSHs(sv_2mortal(newSVpv(file,0)));
        XPUSHs(sv_2mortal(newSViv(buflen)));
        XPUSHs(sv_2mortal(newSViv(off)));
+       XPUSHs(fi->fh==0 ? &PL_sv_undef : (SV *)fi->fh);
        PUTBACK;
        rv = call_sv(_PLfuse_callbacks[15],G_SCALAR);
        SPAGAIN;
@@ -489,6 +546,7 @@ int _PLfuse_write (const char *file, const char *buf, size_t buflen, off_t off,
        XPUSHs(sv_2mortal(newSVpv(file,0)));
        XPUSHs(sv_2mortal(newSVpvn(buf,buflen)));
        XPUSHs(sv_2mortal(newSViv(off)));
+       XPUSHs(fi->fh==0 ? &PL_sv_undef : (SV *)fi->fh);
        PUTBACK;
        rv = call_sv(_PLfuse_callbacks[16],G_SCALAR);
        SPAGAIN;
@@ -557,6 +615,7 @@ int _PLfuse_flush (const char *file, struct fuse_file_info *fi) {
        SAVETMPS;
        PUSHMARK(SP);
        XPUSHs(sv_2mortal(newSVpv(file,0)));
+       XPUSHs(fi->fh==0 ? &PL_sv_undef : (SV *)fi->fh);
        PUTBACK;
        rv = call_sv(_PLfuse_callbacks[18],G_SCALAR);
        SPAGAIN;
@@ -582,6 +641,7 @@ int _PLfuse_release (const char *file, struct fuse_file_info *fi) {
        PUSHMARK(SP);
        XPUSHs(sv_2mortal(newSVpv(file,0)));
        XPUSHs(sv_2mortal(newSViv(flags)));
+       XPUSHs(fi->fh==0 ? &PL_sv_undef : (SV *)fi->fh);
        PUTBACK;
        rv = call_sv(_PLfuse_callbacks[19],G_SCALAR);
        SPAGAIN;
@@ -589,6 +649,14 @@ int _PLfuse_release (const char *file, struct fuse_file_info *fi) {
                rv = POPi;
        else
                rv = 0;
+       /* We're now finished with the handle that we were given, so
+        * we should decrement its count so that it can be freed.
+        */
+       if (fi->fh != 0)
+       {
+               SvREFCNT_dec((SV *)fi->fh);
+               fi->fh = 0;
+       }
        FREETMPS;
        LEAVE;
        PUTBACK;
@@ -607,6 +675,7 @@ int _PLfuse_fsync (const char *file, int datasync, struct fuse_file_info *fi) {
        PUSHMARK(SP);
        XPUSHs(sv_2mortal(newSVpv(file,0)));
        XPUSHs(sv_2mortal(newSViv(flags)));
+       XPUSHs(fi->fh==0 ? &PL_sv_undef : (SV *)fi->fh);
        PUTBACK;
        rv = call_sv(_PLfuse_callbacks[20],G_SCALAR);
        SPAGAIN;
@@ -876,7 +945,7 @@ perl_fuse_main(...)
                        _PLfuse_callbacks[i] = var;
                } else
                if(SvOK(var)) {
-                       croak("invalid callback passed to perl_fuse_main "
+                       croak("invalid callback (%i) passed to perl_fuse_main "
                              "(%s is not a string, code ref, or undef).\n",
                              i+4,SvPVbyte_nolen(var));
                }
index ed7ce57..f25a997 100644 (file)
@@ -1,7 +1,9 @@
 #!/usr/bin/perl -w
 use strict;
 
-use blib;
+use Data::Dumper;
+
+#use blib;
 use Fuse qw(fuse_get_context);
 use POSIX qw(ENOENT EISDIR EINVAL);
 
@@ -63,19 +65,25 @@ sub e_getdir {
 
 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");
+    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;
-       print("open ok\n");
-       return 0;
+    
+    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) = @_;
+    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;