README.md in ffi-libfuse-0.0.1.rctest12 vs README.md in ffi-libfuse-0.1.0.rc20220550
- old
+ new
@@ -1,64 +1,163 @@
# FFI::Libfuse
Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
-## Writing a FUSE Filesystem
+## Requirements
-Create a class that implements the abstract methods of {FFI::Libfuse::Main} and {FFI::Libfuse::FuseOperations}
+ * Ruby 2.7
+ * Linux: libfuse (Fuse2) or libfuse3 (Fuse3)
+ * MacOS: macFuse (https://osxfuse.github.io/)
-Call {FFI::Libfuse.fuse_main} to start the filesystem
+## Building a FUSE Filesystem
+Install the gem
+
+```bash
+gem install ffi-libfuse
+```
+
+Create a filesystem class
+
+* implement FUSE callbacks for filesystem operations satisfying {FFI::Libfuse::FuseOperations}
+* recommend including {FFI::Libfuse::Adapter::Ruby} to add some ruby sugar and safety to the native FUSE Callbacks
+* recommend including {FFI::Libfuse::Adapter::Fuse2Compat} for compatibility with Fuse2/macFuse
+* implement {FFI::Libfuse::Main} configuration methods, eg to parse custom options with {FFI::Libfuse::FuseArgs#parse!}
+ (as altered by any included adapters from {FFI::Libfuse::Adapter})
+* Provide an entrypoint to start the filesystem using {FFI::Libfuse::Main.fuse_main}
+
+{FFI::Libfuse::Filesystem} contains additional classes and modules to help build and compose filesystems
+
+<!-- SAMPLE BEGIN: sample/hello_fs.rb -->
+*sample/hello_fs.rb*
+
```ruby
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
require 'ffi/libfuse'
-class MyFS
- # include helpers to abstract away from the native C callbacks to more idiomatic Ruby
+# Hello World!
+class HelloFS
include FFI::Libfuse::Adapter::Ruby
-
- # ... FUSE callbacks .... quacks like a FFI:Libfuse::FuseOperations
- def getattr(*args)
- #...
+ include FFI::Libfuse::Adapter::Fuse2Compat
+
+ # FUSE Configuration methods
+
+ def fuse_options(args)
+ args.parse!({ 'subject=' => :subject }) do |key:, value:, **|
+ raise FFI::Libfuse::Error, 'subject option must be at least 2 characters' unless value.size >= 2
+
+ @subject = value if key == :subject
+ :handled
+ end
end
-
- def readdir(*args)
- #...
+
+ def fuse_help
+ '-o subject=<subject> a target to say hello to'
end
-
+
+ def fuse_configure
+ @subject ||= 'World!'
+ @content = "Hello #{@subject}\n"
+ end
+
+ # FUSE callbacks
+
+ def getattr(path, stat, *_args)
+ case path
+ when '/'
+ stat.directory(mode: 0o550)
+ when '/hello.txt'
+ stat.file(mode: 0o440, size: @content.size)
+ else
+ raise Errno::ENOENT
+ end
+ end
+
+ def readdir(_path, *_args)
+ yield 'hello.txt'
+ end
+
+ def read(_path, *_args)
+ @content
+ end
end
-FFI::Libfuse::fuse_main(operations: MyFS.new) if __FILE__ == $0
+# Start the file system
+FFI::Libfuse.fuse_main(operations: HelloFS.new) if __FILE__ == $0
+
```
+<!-- SAMPLE END: sample/hello_fs.rb -->
-# Fuse2/Fuse3 compatibility
+Mount the filesystem
+```bash
+hello_fs.rb -h # show help
+hello_fs.rb /mnt/hello # run deamonized, mounted at /mnt/hello
+```
+
+Do file things
+
+```bash
+ls /mnt/hello
+cat /mnt/hello/hello.txt
+```
+
+## Fuse2/Fuse3 compatibility
+
FFI::Libfuse will prefer Fuse3 over Fuse2 by default. See {FFI::Libfuse::LIBFUSE}
-For writing filesystems with backwards/forwards compatibility between fuse version see
-{FFI::Libfuse::Adapter::Fuse2Compat} and {FFI::Libfuse::Adapter::Fuse3Support}
+New filesystems should write for Fuse3 API and include {FFI::Libfuse::Adapter::Fuse2Compat} for backwards compatibility
+Alternatively filesystems written against Fuse2 API can include {FFI::Libfuse::Adapter::Fuse3Support}
+
## MACFuse
-[macFUSE](https://osxfuse.github.io/) (previously OSXFuse) supports a superset of the Fuse2 api so FFI::Libfuse is
- intended to work in that environment.
+[macFUSE](https://osxfuse.github.io/) (previously OSXFuse) supports a superset of the Fuse2 api
-# Multi-threading
+**TODO** Implement macFuse extensions
-Most Ruby filesystems are unlikely to benefit from multi-threaded operation so
-{FFI::Libfuse.fuse_main} as shown above injects the '-s' (single-thread) option by default.
-Pass the original options in directly if multi-threaded operation is desired for your filesystem
+# Under the hood
+{FFI::Libfuse} provides raw access to the underlying libfuse but there some constraints imposed by Ruby.
+
+## Low-level functions re-implemented in Ruby
+
+The C functions fuse_main(), fuse_daemonize() and fuse_loop<_mt>() are re-implemented to provide
+
+* dynamic compatibility between Fuse2 and Fuse3
+* support for multi-threading under MRI
+* signal handling in ruby filesystem (eg HUP to reload)
+
+The `-o native' option will use the native C functions but only exists to assist with testing that FFI::Libfuse has
+similar behaviour to C libfuse.
+
+See {FFI::Libfuse::Main} and {FFI::Libfuse::FuseCommon}
+
+## Multi-threading
+
+{FFI::Libfuse.fuse_main} forces the `-s` (single-thread) option to be set since most Ruby filesystems are
+unlikely to benefit from the overhead caused by multi-threaded operation obtaining/releasing the GVL around each
+callback.
+
+{FFI::Libfuse::Main.fuse_main} does not pass any options by default and should be used in situations where
+multi-threaded operations may be desirable.
+
```ruby
-FFI::Libfuse::fuse_main($0,*ARGV, operations: MyFS.new) if __FILE__ == $0
+FFI::Libfuse::Main.fuse_main(operations: MyFS.new) if __FILE__ == $0
```
-The {FFI::Libfuse::ThreadPool} can be configured with `-o max_threads=<n>,max_idle_threads=<n>` options
+The multi-thread loop uses {FFI::Libfuse::ThreadPool} to control thread usage and can be configured with options
+`-o max_threads=<n>,max_idle_threads=<n>`
-Callbacks that are about to block (and release the GVL for MRI) should call {FFI::Libfuse::ThreadPool.busy}.
+Callbacks that are about to block (and release the GVL for MRI) should call {FFI::Libfuse::ThreadPool.busy} which will
+spawn additional worker threads as required.
-A typical scenario would be a filesystem where some callbacks are blocking on network IO.
+Note that uncaught exceptions in callbacks will kill the worker thread and if all worker threads are dead the
+file system will stop and unmount. In particular if the first callback raises an exception
```ruby
def read(*args)
# prep, validate args etc.. (MRI holding the GVL anyway)
FFI::Libfuse::ThreadPool.busy
@@ -68,32 +167,16 @@
**Note** Fuse itself has conditions under which filesystem callbacks will be serialized. In particular see
[this discussion](http://fuse.996288.n3.nabble.com/GetAttr-calls-being-serialised-td11741.html)
on the serialisation of `#getattr` and `#readdir` calls.
-## Under the hood
+**TODO** Build an example filesystem that makes use of multi-threading
-FFI::Libfuse tries to provide raw access to the underlying libfuse but there some constraints imposed by Ruby.
-The functions fuse_main(), fuse_daemonize() and fuse_loop<_mt>() are re-implemented in Ruby so we can provide
-
- * dynamic compatibility between Fuse2 and Fuse3
- * integrated support for multi-threading under MRI (see {FFI::Libfuse::ThreadPool})
- * signal handling in ruby filesystem (eg HUP to reload)
-
-Sending `-o native' will used the native C functions but this exists to assist with testing that FFI::Libfuse has
-similar behaviour to libfuse itself.
-
-See {FFI::Libfuse::Main} and {FFI::Libfuse::FuseCommon}
-
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/lwoggardner/ffi-libfuse.
-### TODO
- * Include a MetaFS, PathMapperFS etc (possibly a separate library)
- * Build a filesystem that can make use of multi-threaded operations
- * Test with macFUSE
## License
The gem is available under the terms of the [MIT](https://opensource.org/licenses/MIT) License.