From c66dce6e79b980376e51981561de6b52b6f6c364 Mon Sep 17 00:00:00 2001 From: Justin Fletcher Date: Sun, 16 May 2010 18:25:07 +0200 Subject: [PATCH] Addition of file handles on open files 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 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 | 21 ++++++++----- Fuse.xs | 73 +++++++++++++++++++++++++++++++++++++++++++-- examples/example.pl | 20 +++++++++---- 3 files changed, 98 insertions(+), 16 deletions(-) mode change 100644 => 100755 Fuse.pm mode change 100644 => 100755 Fuse.xs diff --git a/Fuse.pm b/Fuse.pm old mode 100644 new mode 100755 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 new mode 100755 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)); } diff --git a/examples/example.pl b/examples/example.pl index ed7ce57..f25a997 100644 --- a/examples/example.pl +++ b/examples/example.pl @@ -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; -- 2.20.1