ffi-libfuse 0.0.1.rctest12 → 0.1.0.rc20220550

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -1
  3. data/CHANGES.md +14 -0
  4. data/LICENSE +21 -0
  5. data/README.md +127 -44
  6. data/lib/ffi/accessors.rb +6 -6
  7. data/lib/ffi/boolean_int.rb +27 -0
  8. data/lib/ffi/devt.rb +23 -0
  9. data/lib/ffi/encoding.rb +38 -0
  10. data/lib/ffi/gnu_extensions.rb +1 -1
  11. data/lib/ffi/libfuse/ackbar.rb +3 -3
  12. data/lib/ffi/libfuse/adapter/context.rb +12 -10
  13. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +52 -51
  14. data/lib/ffi/libfuse/adapter/fuse3_support.rb +0 -1
  15. data/lib/ffi/libfuse/adapter/ruby.rb +499 -148
  16. data/lib/ffi/libfuse/adapter/safe.rb +1 -1
  17. data/lib/ffi/libfuse/adapter.rb +1 -2
  18. data/lib/ffi/libfuse/callbacks.rb +1 -1
  19. data/lib/ffi/libfuse/filesystem/accounting.rb +116 -0
  20. data/lib/ffi/libfuse/filesystem/mapped_dir.rb +74 -0
  21. data/lib/ffi/libfuse/filesystem/mapped_files.rb +141 -0
  22. data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +55 -0
  23. data/lib/ffi/libfuse/filesystem/pass_through_file.rb +45 -0
  24. data/lib/ffi/libfuse/filesystem/utils.rb +102 -0
  25. data/lib/ffi/libfuse/filesystem/virtual_dir.rb +306 -0
  26. data/lib/ffi/libfuse/filesystem/virtual_file.rb +94 -0
  27. data/lib/ffi/libfuse/filesystem/virtual_fs.rb +188 -0
  28. data/lib/ffi/libfuse/filesystem/virtual_node.rb +101 -0
  29. data/lib/ffi/libfuse/filesystem.rb +25 -0
  30. data/lib/ffi/libfuse/fuse2.rb +21 -21
  31. data/lib/ffi/libfuse/fuse3.rb +12 -12
  32. data/lib/ffi/libfuse/fuse_args.rb +69 -34
  33. data/lib/ffi/libfuse/fuse_buffer.rb +128 -26
  34. data/lib/ffi/libfuse/fuse_callbacks.rb +1 -5
  35. data/lib/ffi/libfuse/fuse_common.rb +55 -61
  36. data/lib/ffi/libfuse/fuse_config.rb +134 -143
  37. data/lib/ffi/libfuse/fuse_conn_info.rb +310 -134
  38. data/lib/ffi/libfuse/fuse_context.rb +45 -3
  39. data/lib/ffi/libfuse/fuse_operations.rb +43 -19
  40. data/lib/ffi/libfuse/fuse_version.rb +10 -6
  41. data/lib/ffi/libfuse/main.rb +80 -37
  42. data/lib/ffi/libfuse/version.rb +1 -1
  43. data/lib/ffi/libfuse.rb +13 -4
  44. data/lib/ffi/ruby_object.rb +1 -1
  45. data/lib/ffi/stat/constants.rb +9 -0
  46. data/lib/ffi/stat/native.rb +36 -6
  47. data/lib/ffi/stat/time_spec.rb +26 -10
  48. data/lib/ffi/stat.rb +111 -22
  49. data/lib/ffi/stat_vfs.rb +59 -1
  50. data/lib/ffi/struct_wrapper.rb +22 -1
  51. data/sample/hello_fs.rb +54 -0
  52. data/sample/memory_fs.rb +5 -181
  53. data/sample/no_fs.rb +20 -21
  54. data/sample/pass_through_fs.rb +30 -0
  55. metadata +77 -4
  56. data/lib/ffi/libfuse/adapter/thread_local_context.rb +0 -36
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8cc15469bfd68dbe59e7b5dfab82fc1b34fe51e7e3b61e44c686d8a8025ec5ff
4
- data.tar.gz: 691758dbb28272d60cfc1f5f2f37e8cef56de15412706b303f181c5b31cdf03f
3
+ metadata.gz: d71e73d4261d20d3e8318fa01517342da232c0167f4febd73249fb99bb314e15
4
+ data.tar.gz: 5d478d73897b3d590b1c23ddc656f416574865a53db88bb078ddc949c310bc81
5
5
  SHA512:
6
- metadata.gz: 6dfefe8f3a512dcaa6f2ca34c3f45cba0fb61e17af4dc0114b9fec6a1adc7a63b0f156415b69e256b9bae97d5a9574626001e285f85ddaae985df84d840b2ca3
7
- data.tar.gz: cb4d1f15b5220c9ec4c16a2c0874c082b17ca23a9f725d4bc8e62f5eb69f1e02abaa3960dfef6711dd059ef38e7d97abe2e4af69e68a630bf8728ab3afbfb0ad
6
+ metadata.gz: bfc6ff5b7a9e706be919eaf35fbdfe7b3c2575c68e03008f2381c723ab6896f9826047427e78e8915b90985c0ce26e04e83e639817dd5b7f40bbe1adc69e8aa8
7
+ data.tar.gz: ac2c8aa63ee627f73cd2d8a06071273bbbc8af64f9ae21862ff6f9bc235c7a0aa031c4fe601aea2b7e3ad4f93ef8ffd248fba35fbcf7cbabf422c37727995f72
data/.yardopts CHANGED
@@ -1 +1,3 @@
1
- --markup=markdown
1
+ --markup=markdown
2
+ -
3
+ CHANGES.md
data/CHANGES.md ADDED
@@ -0,0 +1,14 @@
1
+ 0.1.0 / 2022-04
2
+ ------------------
3
+
4
+ #### BREAKING changes
5
+ * Changed option parsing.
6
+
7
+ {FFI::Libfuse::Main#fuse_options} now takes a FuseArgs parameter and fuse_opt_proc is not used
8
+
9
+ #### New Features
10
+ * Implemented helper filesystems in {FFI::Libfuse::Filesystem}
11
+
12
+ #### Fixes
13
+ * Test on OSX with macFuse
14
+ * Lots
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Grant Gardner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -2,61 +2,160 @@
2
2
 
3
3
  Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
4
4
 
5
- ## Writing a FUSE Filesystem
5
+ ## Requirements
6
6
 
7
- Create a class that implements the abstract methods of {FFI::Libfuse::Main} and {FFI::Libfuse::FuseOperations}
7
+ * Ruby 2.7
8
+ * Linux: libfuse (Fuse2) or libfuse3 (Fuse3)
9
+ * MacOS: macFuse (https://osxfuse.github.io/)
8
10
 
9
- Call {FFI::Libfuse.fuse_main} to start the filesystem
11
+ ## Building a FUSE Filesystem
12
+
13
+ Install the gem
14
+
15
+ ```bash
16
+ gem install ffi-libfuse
17
+ ```
18
+
19
+ Create a filesystem class
20
+
21
+ * implement FUSE callbacks for filesystem operations satisfying {FFI::Libfuse::FuseOperations}
22
+ * recommend including {FFI::Libfuse::Adapter::Ruby} to add some ruby sugar and safety to the native FUSE Callbacks
23
+ * recommend including {FFI::Libfuse::Adapter::Fuse2Compat} for compatibility with Fuse2/macFuse
24
+ * implement {FFI::Libfuse::Main} configuration methods, eg to parse custom options with {FFI::Libfuse::FuseArgs#parse!}
25
+ (as altered by any included adapters from {FFI::Libfuse::Adapter})
26
+ * Provide an entrypoint to start the filesystem using {FFI::Libfuse::Main.fuse_main}
27
+
28
+ {FFI::Libfuse::Filesystem} contains additional classes and modules to help build and compose filesystems
29
+
30
+ <!-- SAMPLE BEGIN: sample/hello_fs.rb -->
31
+ *sample/hello_fs.rb*
10
32
 
11
33
  ```ruby
34
+ #!/usr/bin/env ruby
35
+ # frozen_string_literal: true
36
+
12
37
  require 'ffi/libfuse'
13
38
 
14
- class MyFS
15
- # include helpers to abstract away from the native C callbacks to more idiomatic Ruby
39
+ # Hello World!
40
+ class HelloFS
16
41
  include FFI::Libfuse::Adapter::Ruby
17
-
18
- # ... FUSE callbacks .... quacks like a FFI:Libfuse::FuseOperations
19
- def getattr(*args)
20
- #...
42
+ include FFI::Libfuse::Adapter::Fuse2Compat
43
+
44
+ # FUSE Configuration methods
45
+
46
+ def fuse_options(args)
47
+ args.parse!({ 'subject=' => :subject }) do |key:, value:, **|
48
+ raise FFI::Libfuse::Error, 'subject option must be at least 2 characters' unless value.size >= 2
49
+
50
+ @subject = value if key == :subject
51
+ :handled
52
+ end
21
53
  end
22
-
23
- def readdir(*args)
24
- #...
54
+
55
+ def fuse_help
56
+ '-o subject=<subject> a target to say hello to'
57
+ end
58
+
59
+ def fuse_configure
60
+ @subject ||= 'World!'
61
+ @content = "Hello #{@subject}\n"
62
+ end
63
+
64
+ # FUSE callbacks
65
+
66
+ def getattr(path, stat, *_args)
67
+ case path
68
+ when '/'
69
+ stat.directory(mode: 0o550)
70
+ when '/hello.txt'
71
+ stat.file(mode: 0o440, size: @content.size)
72
+ else
73
+ raise Errno::ENOENT
74
+ end
75
+ end
76
+
77
+ def readdir(_path, *_args)
78
+ yield 'hello.txt'
79
+ end
80
+
81
+ def read(_path, *_args)
82
+ @content
25
83
  end
26
-
27
84
  end
28
85
 
29
- FFI::Libfuse::fuse_main(operations: MyFS.new) if __FILE__ == $0
86
+ # Start the file system
87
+ FFI::Libfuse.fuse_main(operations: HelloFS.new) if __FILE__ == $0
88
+
30
89
  ```
90
+ <!-- SAMPLE END: sample/hello_fs.rb -->
31
91
 
32
- # Fuse2/Fuse3 compatibility
92
+ Mount the filesystem
93
+
94
+ ```bash
95
+ hello_fs.rb -h # show help
96
+ hello_fs.rb /mnt/hello # run deamonized, mounted at /mnt/hello
97
+ ```
98
+
99
+ Do file things
100
+
101
+ ```bash
102
+ ls /mnt/hello
103
+ cat /mnt/hello/hello.txt
104
+ ```
105
+
106
+ ## Fuse2/Fuse3 compatibility
33
107
 
34
108
  FFI::Libfuse will prefer Fuse3 over Fuse2 by default. See {FFI::Libfuse::LIBFUSE}
35
109
 
36
- For writing filesystems with backwards/forwards compatibility between fuse version see
37
- {FFI::Libfuse::Adapter::Fuse2Compat} and {FFI::Libfuse::Adapter::Fuse3Support}
110
+ New filesystems should write for Fuse3 API and include {FFI::Libfuse::Adapter::Fuse2Compat} for backwards compatibility
111
+
112
+ Alternatively filesystems written against Fuse2 API can include {FFI::Libfuse::Adapter::Fuse3Support}
38
113
 
39
114
  ## MACFuse
40
115
 
41
- [macFUSE](https://osxfuse.github.io/) (previously OSXFuse) supports a superset of the Fuse2 api so FFI::Libfuse is
42
- intended to work in that environment.
116
+ [macFUSE](https://osxfuse.github.io/) (previously OSXFuse) supports a superset of the Fuse2 api
117
+
118
+ **TODO** Implement macFuse extensions
119
+
120
+
121
+ # Under the hood
122
+
123
+ {FFI::Libfuse} provides raw access to the underlying libfuse but there some constraints imposed by Ruby.
124
+
125
+ ## Low-level functions re-implemented in Ruby
43
126
 
44
- # Multi-threading
127
+ The C functions fuse_main(), fuse_daemonize() and fuse_loop<_mt>() are re-implemented to provide
45
128
 
46
- Most Ruby filesystems are unlikely to benefit from multi-threaded operation so
47
- {FFI::Libfuse.fuse_main} as shown above injects the '-s' (single-thread) option by default.
129
+ * dynamic compatibility between Fuse2 and Fuse3
130
+ * support for multi-threading under MRI
131
+ * signal handling in ruby filesystem (eg HUP to reload)
48
132
 
49
- Pass the original options in directly if multi-threaded operation is desired for your filesystem
133
+ The `-o native' option will use the native C functions but only exists to assist with testing that FFI::Libfuse has
134
+ similar behaviour to C libfuse.
135
+
136
+ See {FFI::Libfuse::Main} and {FFI::Libfuse::FuseCommon}
137
+
138
+ ## Multi-threading
139
+
140
+ {FFI::Libfuse.fuse_main} forces the `-s` (single-thread) option to be set since most Ruby filesystems are
141
+ unlikely to benefit from the overhead caused by multi-threaded operation obtaining/releasing the GVL around each
142
+ callback.
143
+
144
+ {FFI::Libfuse::Main.fuse_main} does not pass any options by default and should be used in situations where
145
+ multi-threaded operations may be desirable.
50
146
 
51
147
  ```ruby
52
- FFI::Libfuse::fuse_main($0,*ARGV, operations: MyFS.new) if __FILE__ == $0
148
+ FFI::Libfuse::Main.fuse_main(operations: MyFS.new) if __FILE__ == $0
53
149
  ```
54
150
 
55
- The {FFI::Libfuse::ThreadPool} can be configured with `-o max_threads=<n>,max_idle_threads=<n>` options
151
+ The multi-thread loop uses {FFI::Libfuse::ThreadPool} to control thread usage and can be configured with options
152
+ `-o max_threads=<n>,max_idle_threads=<n>`
56
153
 
57
- Callbacks that are about to block (and release the GVL for MRI) should call {FFI::Libfuse::ThreadPool.busy}.
154
+ Callbacks that are about to block (and release the GVL for MRI) should call {FFI::Libfuse::ThreadPool.busy} which will
155
+ spawn additional worker threads as required.
58
156
 
59
- A typical scenario would be a filesystem where some callbacks are blocking on network IO.
157
+ Note that uncaught exceptions in callbacks will kill the worker thread and if all worker threads are dead the
158
+ file system will stop and unmount. In particular if the first callback raises an exception
60
159
 
61
160
  ```ruby
62
161
  def read(*args)
@@ -70,29 +169,13 @@ end
70
169
  [this discussion](http://fuse.996288.n3.nabble.com/GetAttr-calls-being-serialised-td11741.html)
71
170
  on the serialisation of `#getattr` and `#readdir` calls.
72
171
 
73
- ## Under the hood
172
+ **TODO** Build an example filesystem that makes use of multi-threading
74
173
 
75
- FFI::Libfuse tries to provide raw access to the underlying libfuse but there some constraints imposed by Ruby.
76
-
77
- The functions fuse_main(), fuse_daemonize() and fuse_loop<_mt>() are re-implemented in Ruby so we can provide
78
-
79
- * dynamic compatibility between Fuse2 and Fuse3
80
- * integrated support for multi-threading under MRI (see {FFI::Libfuse::ThreadPool})
81
- * signal handling in ruby filesystem (eg HUP to reload)
82
-
83
- Sending `-o native' will used the native C functions but this exists to assist with testing that FFI::Libfuse has
84
- similar behaviour to libfuse itself.
85
-
86
- See {FFI::Libfuse::Main} and {FFI::Libfuse::FuseCommon}
87
174
 
88
175
  ## Contributing
89
176
 
90
177
  Bug reports and pull requests are welcome on GitHub at https://github.com/lwoggardner/ffi-libfuse.
91
178
 
92
- ### TODO
93
- * Include a MetaFS, PathMapperFS etc (possibly a separate library)
94
- * Build a filesystem that can make use of multi-threaded operations
95
- * Test with macFUSE
96
179
 
97
180
  ## License
98
181
 
data/lib/ffi/accessors.rb CHANGED
@@ -20,8 +20,8 @@ module FFI
20
20
  #
21
21
  # Define a struct attribute reader for members
22
22
  # @param [Array<Symbol>] attrs the attribute names
23
- # @param [String] format
24
- # A format string containing a single %s to convert attr symbol to struct member
23
+ # @param [Proc|String] format
24
+ # A Proc, or format string containing a single %s, to convert attr to struct member name
25
25
  # @param [Boolean] simple
26
26
  # Controls how writer methods are defined using block
27
27
  # @param [Proc] block
@@ -33,7 +33,7 @@ module FFI
33
33
  # @return [void]
34
34
  def ffi_attr_reader(*attrs, format: '%s', simple: true, &block)
35
35
  attrs.each do |attr|
36
- member = (format % attr).to_sym
36
+ member = (format.respond_to?(:call) ? format.call(attr) : format % attr).to_sym
37
37
  ffi_attr_readers[attr] = member
38
38
  if !block
39
39
  define_method(attr) { self[member] }
@@ -47,7 +47,7 @@ module FFI
47
47
 
48
48
  # Define a struct attribute writer
49
49
  # @param [Array<Symbol>] attrs the attribute names
50
- # @param [String] format
50
+ # @param [String|Proc] format
51
51
  # A format string containing a single %s to convert attr symbol to struct member
52
52
  # @param [Boolean] simple
53
53
  # Controls how writer methods are defined using block
@@ -55,8 +55,8 @@ module FFI
55
55
  # An optional block to set the input value into the struct field.
56
56
  #
57
57
  # If simple is true then the struct field is set to the result of calling block with the input value,
58
- # otherwise the method is defined directly from the block. Use __method__[0..-2] to get the attribute name
59
- # and self.class.ffi_attr_writers[__method__[0..-2]] to get the struct field name
58
+ # otherwise the method is defined directly from the block. Use __method__[0..-1] to get the attribute name
59
+ # and self.class.ffi_attr_writers[__method__[0..-1]] to get the struct member name
60
60
  # @return [void]
61
61
  def ffi_attr_writer(*attrs, format: '%s', simple: true, &block)
62
62
  attrs.each do |attr|
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFI
4
+ # Converter generator for different sizes ints as boolean
5
+ class BooleanInt
6
+ include DataConverter
7
+ attr_reader :native_type
8
+
9
+ def initialize(int_type)
10
+ @native_type = FFI.find_type(int_type)
11
+ end
12
+
13
+ # Falsey = 0, Truthy = 1
14
+ def to_native(obj, _context)
15
+ obj ? 1 : 0
16
+ end
17
+
18
+ # Not Zero
19
+ def from_native(object_id, _context)
20
+ !object_id.zero?
21
+ end
22
+
23
+ %i[char short int long int8 int16 int32 int64].each do |t|
24
+ FFI.typedef(BooleanInt.new(t), "bool_#{t}".to_sym)
25
+ end
26
+ end
27
+ end
data/lib/ffi/devt.rb CHANGED
@@ -26,5 +26,28 @@ module FFI
26
26
  # @param [Integer] dev
27
27
  # @return [Integer] the minor component of dev
28
28
  attach_function :minor, "#{prefix}minor".to_sym, [:int], :int
29
+ rescue FFI::NotFoundError
30
+ case Platform::NAME
31
+ when 'x86_64-darwin'
32
+ # From https://github.com/golang/go/issues/8106 these functions are not defined on Darwin.
33
+ class << self
34
+ # define major(x) ((int32_t)(((u_int32_t)(x) >> 24) & 0xff))
35
+ def major(dev)
36
+ (dev >> 24) & 0xff
37
+ end
38
+
39
+ # define minor(x) ((int32_t)((x) & 0xffffff))
40
+ def minor(dev)
41
+ (dev & 0xffffff)
42
+ end
43
+
44
+ # define makedev(x,y) ((dev_t)(((x) << 24) | (y)))
45
+ def makedev(major, minor)
46
+ (major << 24) | minor
47
+ end
48
+ end
49
+ else
50
+ raise
51
+ end
29
52
  end
30
53
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ module FFI
6
+ # DataConverter for reading/writing encoded strings
7
+ class Encoding
8
+ include DataConverter
9
+
10
+ attr_reader :encoding
11
+
12
+ # Create a dataconverter for the specified encoding
13
+ # @example
14
+ # module MyFFIModule
15
+ # extend FFI::Library
16
+ # typedef FFI::Encoding.for('utf8'), :utf8_string
17
+ # end
18
+ def self.for(encoding)
19
+ new(encoding)
20
+ end
21
+
22
+ def from_native(value, _ctx)
23
+ value.force_encoding(encoding)
24
+ end
25
+
26
+ def to_native(value, _ctx)
27
+ value.encode(encoding)
28
+ end
29
+
30
+ def native_type(_type = nil)
31
+ FFI::Type::STRING
32
+ end
33
+
34
+ def initialize(encoding)
35
+ @encoding = encoding
36
+ end
37
+ end
38
+ end
@@ -27,7 +27,7 @@ module FFI
27
27
  module GNUExtensions
28
28
  if FFI::Platform::IS_GNU
29
29
  extend FFI::Library
30
- ffi_lib 'libdl'
30
+ ffi_lib(%w[libdl.so libdl.so.2].flat_map { |l| [l, "#{FFI::Platform::NAME}-gnu/#{l}"] })
31
31
 
32
32
  # @!method dlopen(library,type)
33
33
  # @return [FFI::Pointer] library address, possibly NULL
@@ -29,15 +29,15 @@ module FFI
29
29
  end
30
30
 
31
31
  # @param [Hash<Symbol|String|Integer,String|Proc>] traps
32
- # Map of signal or signo to signal handler as per Signal.trap
32
+ # Map of signal or signo to signal handler as per {::Signal.trap}
33
33
  # @param [Boolean] force
34
- # If not set traps that are not currently set to 'DEFAULT' will be ignored
34
+ # Unless set, only installs traps currently set to 'DEFAULT'.
35
35
  def initialize(traps, force: false, signal: Signal)
36
36
  @signal = signal
37
37
  @traps = traps.transform_keys { |k| signame(k) }
38
38
  @pr, @pw = ::IO.pipe
39
39
  @monitor = nil
40
- @restore = @traps.map { |(sig, handler)| [sig, trap(sig, handler, force: force)] }.to_h
40
+ @restore = @traps.to_h { |sig, handler| [sig, trap(sig, handler, force: force)] }
41
41
  end
42
42
 
43
43
  # Handle the next available signal on the pipe (without blocking)
@@ -5,15 +5,14 @@ require_relative '../fuse_context'
5
5
  module FFI
6
6
  module Libfuse
7
7
  module Adapter
8
- # Wrapper module to inject {FuseContext} as first arg to each callback method (except :destroy)
9
- #
10
- # {ThreadLocalContext} may be a less intrusive means to make the context available to callbacks
8
+ # Injects a wrapper via #{FuseCallbacks#fuse_wrappers} make the current {FuseContext} object available to
9
+ # callbacks (except :destroy) via thread local variable :fuse_context
11
10
  module Context
12
11
  # @!visibility private
13
12
  def fuse_wrappers(*wrappers)
14
13
  wrappers.unshift(
15
14
  {
16
- wrapper: proc { |_fm, *args, **_, &b| self.class.context_callback(*args, &b) },
15
+ wrapper: proc { |_fm, *args, **_, &b| Context.thread_local_context(*args, &b) },
17
16
  excludes: %i[destroy]
18
17
  }
19
18
  )
@@ -24,12 +23,15 @@ module FFI
24
23
 
25
24
  module_function
26
25
 
27
- # @yieldparam [FuseContext] ctx
28
- # @yieldparam [Array] *args
29
- def context_callback(*args)
30
- ctx = FuseContext.get
31
- ctx = nil if ctx.null?
32
- yield ctx, *args
26
+ # Capture {FuseContext} in thread local variable
27
+ def fuse_context
28
+ Thread.current[:fuse_context] ||= FuseContext.get
29
+ end
30
+
31
+ def thread_local_context(*args)
32
+ yield(*args)
33
+ ensure
34
+ Thread.current[:fuse_context] = nil
33
35
  end
34
36
  end
35
37
  end
@@ -5,77 +5,78 @@ module FFI
5
5
  module Adapter
6
6
  # Wrapper module to assist filesystem written for Fuse3 to be compatible with Fuse2
7
7
  module Fuse2Compat
8
- # @!visibility private
9
8
  # Wrapper shim for fuse methods to ensure compatibility with Fuse2
10
- module Fuse2Prepend
9
+ module Prepend
11
10
  include Adapter
12
11
 
13
- def getattr(path, stat, fuse_file_info = nil)
14
- super(path, stat, fuse_file_info)
15
- end
16
-
17
- def truncate(path, size, fuse_file_info = nil)
18
- super(path, size, fuse_file_info)
19
- end
12
+ if FUSE_MAJOR_VERSION == 2
13
+ # @!visibility private
14
+ def getattr(path, stat, fuse_file_info = nil)
15
+ super(path, stat, fuse_file_info)
16
+ end
20
17
 
21
- def init(fuse_conn_info, fuse_config = nil)
22
- super(fuse_conn_info, fuse_config)
23
- end
18
+ def truncate(path, size, fuse_file_info = nil)
19
+ super(path, size, fuse_file_info)
20
+ end
24
21
 
25
- def chown(path, uid, gid, fuse_file_info = nil)
26
- super(path, uid, gid, fuse_file_info)
27
- end
22
+ def init(fuse_conn_info, fuse_config = nil)
23
+ super(fuse_conn_info, fuse_config)
24
+ end
28
25
 
29
- def chmod(path, mode, fuse_file_info = nil)
30
- super(path, mode, fuse_file_info)
31
- end
26
+ def chown(path, uid, gid, fuse_file_info = nil)
27
+ super(path, uid, gid, fuse_file_info)
28
+ end
32
29
 
33
- def utimens(path, atime, mtime, fuse_file_info = nil)
34
- super(path, atime, mtime, fuse_file_info)
35
- end
30
+ def chmod(path, mode, fuse_file_info = nil)
31
+ super(path, mode, fuse_file_info)
32
+ end
36
33
 
37
- def readdir(path, buffer, filler, offset, fuse_file_info, fuse_readdir_flag = 0)
38
- filler = proc { |buf, name, stat, off = 0, _fuse_fill_dir_flag = 0| filler.call(buf, name, stat, off) }
39
- super(path, buffer, filler, offset, fuse_file_info, fuse_readdir_flag)
40
- end
34
+ def utimens(path, atime, mtime, fuse_file_info = nil)
35
+ super(path, atime, mtime, fuse_file_info)
36
+ end
41
37
 
42
- def fgetattr(*args)
43
- getattr(*args)
44
- end
38
+ def readdir(path, buffer, filler, offset, fuse_file_info, fuse_readdir_flag = 0)
39
+ f3_fill = proc { |buf, name, stat, off = 0, _fuse_fill_dir_flag = 0| filler.call(buf, name, stat, off) }
40
+ super(path, buffer, f3_fill, offset, fuse_file_info, fuse_readdir_flag)
41
+ end
45
42
 
46
- def ftruncate(*args)
47
- truncate(*args)
48
- end
43
+ def fgetattr(path, stat, ffi)
44
+ stat.clear # For some reason (at least on OSX) the stat is not clear when this is called.
45
+ getattr(path, stat, ffi)
46
+ 0
47
+ end
49
48
 
50
- def fuse_respond_to?(fuse_method)
51
- fuse_method = fuse_method[1..].to_sym if %i[fgetattr ftruncate].include?(fuse_method)
52
- super(fuse_method)
53
- end
49
+ def ftruncate(*args)
50
+ truncate(*args)
51
+ end
54
52
 
55
- def fuse_flags
56
- res = defined?(super) ? super : []
57
- if respond_to?(:init_fuse_config)
58
- fuse_config = FuseConfig.new
59
- init_fuse_config(fuse_config, :fuse2)
60
- res << :nullpath_ok if fuse_config.nullpath_ok?
53
+ def fuse_respond_to?(fuse_method)
54
+ fuse_method = fuse_method[1..].to_sym if %i[fgetattr ftruncate].include?(fuse_method)
55
+ super(fuse_method)
61
56
  end
62
57
 
63
- res
64
- end
65
- end
58
+ def fuse_flags
59
+ res = defined?(super) ? super : []
60
+ if respond_to?(:init_fuse_config)
61
+ fuse_config = FuseConfig.new
62
+ init_fuse_config(fuse_config, :fuse2)
63
+ res << :nullpath_ok if fuse_config.nullpath_ok?
64
+ end
66
65
 
67
- # @!visibility private
68
- module Fuse3Prepend
69
- def init(*args)
70
- init_fuse_config(args.detect { |a| a.is_a?(FuseConfig) }) if respond_to?(:init_fuse_config)
71
- super if defined?(super)
66
+ res
67
+ end
68
+
69
+ else
70
+ def init(*args)
71
+ init_fuse_config(args.detect { |a| a.is_a?(FuseConfig) }) if respond_to?(:init_fuse_config)
72
+ super if defined?(super)
73
+ end
72
74
  end
73
75
  end
74
76
 
75
77
  # @!visibility private
76
78
  def self.included(mod)
77
- prepend_module = Libfuse.FUSE_MAJOR_VERSION == 2 ? Fuse2Prepend : Fuse3Prepend
78
- mod.prepend(prepend_module)
79
+ mod.prepend(Prepend)
79
80
  end
80
81
 
81
82
  # @!method init_fuse_config(fuse_config,compat)
@@ -12,7 +12,6 @@ module FFI
12
12
  #
13
13
  module Fuse3Support
14
14
  # The actual module methods that are prepended
15
- # @!visibility private
16
15
  module Prepend
17
16
  include Adapter
18
17