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.
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);
=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
=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
=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
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;
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;
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;
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;
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;
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;
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;
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;
_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));
}
#!/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);
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;