ffi-libfuse 0.0.1.pre

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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -0
  3. data/README.md +100 -0
  4. data/lib/ffi/accessors.rb +145 -0
  5. data/lib/ffi/devt.rb +30 -0
  6. data/lib/ffi/flock.rb +47 -0
  7. data/lib/ffi/gnu_extensions.rb +115 -0
  8. data/lib/ffi/libfuse/ackbar.rb +112 -0
  9. data/lib/ffi/libfuse/adapter/context.rb +37 -0
  10. data/lib/ffi/libfuse/adapter/debug.rb +89 -0
  11. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +91 -0
  12. data/lib/ffi/libfuse/adapter/fuse3_support.rb +87 -0
  13. data/lib/ffi/libfuse/adapter/interrupt.rb +37 -0
  14. data/lib/ffi/libfuse/adapter/pathname.rb +23 -0
  15. data/lib/ffi/libfuse/adapter/ruby.rb +334 -0
  16. data/lib/ffi/libfuse/adapter/safe.rb +58 -0
  17. data/lib/ffi/libfuse/adapter/thread_local_context.rb +36 -0
  18. data/lib/ffi/libfuse/adapter.rb +79 -0
  19. data/lib/ffi/libfuse/callbacks.rb +61 -0
  20. data/lib/ffi/libfuse/fuse2.rb +159 -0
  21. data/lib/ffi/libfuse/fuse3.rb +162 -0
  22. data/lib/ffi/libfuse/fuse_args.rb +166 -0
  23. data/lib/ffi/libfuse/fuse_buffer.rb +155 -0
  24. data/lib/ffi/libfuse/fuse_callbacks.rb +48 -0
  25. data/lib/ffi/libfuse/fuse_cmdline_opts.rb +44 -0
  26. data/lib/ffi/libfuse/fuse_common.rb +249 -0
  27. data/lib/ffi/libfuse/fuse_config.rb +205 -0
  28. data/lib/ffi/libfuse/fuse_conn_info.rb +211 -0
  29. data/lib/ffi/libfuse/fuse_context.rb +79 -0
  30. data/lib/ffi/libfuse/fuse_file_info.rb +100 -0
  31. data/lib/ffi/libfuse/fuse_loop_config.rb +43 -0
  32. data/lib/ffi/libfuse/fuse_operations.rb +870 -0
  33. data/lib/ffi/libfuse/fuse_opt.rb +54 -0
  34. data/lib/ffi/libfuse/fuse_poll_handle.rb +59 -0
  35. data/lib/ffi/libfuse/fuse_version.rb +43 -0
  36. data/lib/ffi/libfuse/job_pool.rb +53 -0
  37. data/lib/ffi/libfuse/main.rb +200 -0
  38. data/lib/ffi/libfuse/test/operations.rb +56 -0
  39. data/lib/ffi/libfuse/test.rb +3 -0
  40. data/lib/ffi/libfuse/thread_pool.rb +147 -0
  41. data/lib/ffi/libfuse/version.rb +8 -0
  42. data/lib/ffi/libfuse.rb +24 -0
  43. data/lib/ffi/ruby_object.rb +95 -0
  44. data/lib/ffi/stat/constants.rb +29 -0
  45. data/lib/ffi/stat/native.rb +50 -0
  46. data/lib/ffi/stat/time_spec.rb +137 -0
  47. data/lib/ffi/stat.rb +96 -0
  48. data/lib/ffi/stat_vfs.rb +81 -0
  49. data/lib/ffi/struct_array.rb +39 -0
  50. data/lib/ffi/struct_wrapper.rb +100 -0
  51. data/sample/memory_fs.rb +189 -0
  52. data/sample/no_fs.rb +69 -0
  53. metadata +165 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c870a0015b02637bb7423453df5f208dc2a9b263ac5d68ae9de7a71c7767e005
4
+ data.tar.gz: 6ea5d671235ce094dbcbaf1e2257cc8879a102af74fc9a5ebc0cf2238e17a6fd
5
+ SHA512:
6
+ metadata.gz: d80e0f44e8bf7bbb23705c9937c142105aed1eb89bd8cad0f8eba5a8e36b6f590afb35080790df7564f0c2f2f3aec86ef3c99a83a24d8ec3201ca13963ca0e63
7
+ data.tar.gz: 9937c251e92940a61e0e6d01c9b45ce525a2e0045d6cdcc182d377926d8c671ae3e9de07d128e8344a566ff8cc71d06095514e3701d31065faec57e88e9f383e
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup=markdown
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # FFI::Libfuse
2
+
3
+ Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
4
+
5
+ ## Writing a FUSE Filesystem
6
+
7
+ Create a class that implements the abstract methods of {FFI::Libfuse::Main} and {FFI::Libfuse::FuseOperations}
8
+
9
+ Call {FFI::Libfuse.fuse_main} to start the filesystem
10
+
11
+ ```ruby
12
+ require 'ffi/libfuse'
13
+
14
+ class MyFS
15
+ # include helpers to abstract away from the native C callbacks to more idiomatic Ruby
16
+ include FFI::Libfuse::Adapter::Ruby
17
+
18
+ # ... FUSE callbacks .... quacks like a FFI:Libfuse::FuseOperations
19
+ def getattr(*args)
20
+ #...
21
+ end
22
+
23
+ def readdir(*args)
24
+ #...
25
+ end
26
+
27
+ end
28
+
29
+ FFI::Libfuse::fuse_main(operations: MyFS.new) if __FILE__ == $0
30
+ ```
31
+
32
+ # Fuse2/Fuse3 compatibility
33
+
34
+ FFI::Libfuse will prefer Fuse3 over Fuse2 by default. See {FFI::Libfuse::LIBFUSE}
35
+
36
+ For writing filesystems with backwards/forwards compatibility between fuse version see
37
+ {FFI::Libfuse::Adapter::Fuse2Compat} and {FFI::Libfuse::Adapter::Fuse3Support}
38
+
39
+ ## MACFuse
40
+
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.
43
+
44
+ # Multi-threading
45
+
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.
48
+
49
+ Pass the original options in directly if multi-threaded operation is desired for your filesystem
50
+
51
+ ```ruby
52
+ FFI::Libfuse::fuse_main($0,*ARGV, operations: MyFS.new) if __FILE__ == $0
53
+ ```
54
+
55
+ The {FFI::Libfuse::ThreadPool} can be configured with `-o max_threads=<n>,max_idle_threads=<n>` options
56
+
57
+ Callbacks that are about to block (and release the GVL for MRI) should call {FFI::Libfuse::ThreadPool.busy}.
58
+
59
+ A typical scenario would be a filesystem where some callbacks are blocking on network IO.
60
+
61
+ ```ruby
62
+ def read(*args)
63
+ # prep, validate args etc.. (MRI holding the GVL anyway)
64
+ FFI::Libfuse::ThreadPool.busy
65
+ # Now make some REST or other network call to read the data
66
+ end
67
+ ```
68
+
69
+ **Note** Fuse itself has conditions under which filesystem callbacks will be serialized. In particular see
70
+ [this discussion](http://fuse.996288.n3.nabble.com/GetAttr-calls-being-serialised-td11741.html)
71
+ on the serialisation of `#getattr` and `#readdir` calls.
72
+
73
+ ## Under the hood
74
+
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
+
88
+ ## Contributing
89
+
90
+ Bug reports and pull requests are welcome on GitHub at https://github.com/lwoggardner/ffi-libfuse.
91
+
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
+
97
+ ## License
98
+
99
+ The gem is available under the terms of the [MIT](https://opensource.org/licenses/MIT) License.
100
+
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ module FFI
6
+ # Syntax sugar for FFI::Struct
7
+ module Accessors
8
+ # DSL methods for defining struct member accessors
9
+ module ClassMethods
10
+ # Define both a reader and a writer for members
11
+ # @param [Array<Symbol>] attrs the attribute names
12
+ # @param [String] format
13
+ # A format string containing a single %s to convert attr symbol to struct member
14
+ # @return [void]
15
+ def ffi_attr_accessor(*attrs, format: '%s')
16
+ ffi_attr_reader(*attrs, format: format)
17
+ ffi_attr_writer(*attrs, format: format)
18
+ end
19
+
20
+ #
21
+ # Define a struct attribute reader for members
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
25
+ # @param [Boolean] simple
26
+ # Controls how writer methods are defined using block
27
+ # @param [Proc] block
28
+ # An optional block to the struct field(s) into something more useful
29
+ #
30
+ # If simple is true then block takes the struct field value, otherwise method is defined directly from the block
31
+ # and should use __method__ to get the attr name, and self.class.ffi_attr_readers[__method__] to get the member
32
+ # name
33
+ # @return [void]
34
+ def ffi_attr_reader(*attrs, format: '%s', simple: true, &block)
35
+ attrs.each do |attr|
36
+ member = (format % attr).to_sym
37
+ ffi_attr_readers[attr] = member
38
+ if !block
39
+ define_method(attr) { self[member] }
40
+ elsif simple
41
+ define_method(attr) { block.call(self[member]) }
42
+ else
43
+ define_method(attr, &block)
44
+ end
45
+ end
46
+ end
47
+
48
+ # Define a struct attribute writer
49
+ # @param [Array<Symbol>] attrs the attribute names
50
+ # @param [String] format
51
+ # A format string containing a single %s to convert attr symbol to struct member
52
+ # @param [Boolean] simple
53
+ # Controls how writer methods are defined using block
54
+ # @param [Proc] block
55
+ # An optional block to set the input value into the struct field.
56
+ #
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
60
+ # @return [void]
61
+ def ffi_attr_writer(*attrs, format: '%s', simple: true, &block)
62
+ attrs.each do |attr|
63
+ member = (format % attr).to_sym
64
+ ffi_attr_writers[attr.to_sym] = member
65
+ if !block
66
+ define_method("#{attr}=") { |val| self[member] = val }
67
+ elsif simple
68
+ define_method("#{attr}=") { |val| self[member] = block.call(val) }
69
+ else
70
+ define_method("#{attr}=", &block)
71
+ end
72
+ end
73
+ end
74
+
75
+ # All defined readers
76
+ # @return [Hash<Symbol,Symbol>] map of attr names to member names for which readers exist
77
+ def ffi_attr_readers
78
+ @ffi_attr_readers ||= {}
79
+ end
80
+
81
+ # All defined writers
82
+ # @return [Hash<Symbol,Symbol>] map of attr names to member names for which writers exist
83
+ def ffi_attr_writers
84
+ @ffi_attr_writers ||= {}
85
+ end
86
+
87
+ # Define individual flag accessors over a bitmask field
88
+ def ffi_bitflag_accessor(attr, *flags)
89
+ ffi_bitflag_reader(attr, *flags)
90
+ ffi_bitflag_writer(attr, *flags)
91
+ end
92
+
93
+ # Define individual flag readers over a bitmask field
94
+ # @param [Symbol] attr the bitmask member
95
+ # @param [Array<Symbol>] flags list of flags
96
+ # @return [void]
97
+ def ffi_bitflag_reader(attr, *flags)
98
+ flags.each { |f| ffi_attr_reader(f, simple: false) { self[attr].include?(f) } }
99
+ end
100
+
101
+ # Define individual flag writers over a bitmask field
102
+ # @param [Symbol] attr the bitmask member
103
+ # @param [Array<Symbol>] flags list of flags
104
+ # @return [void]
105
+ def ffi_bitflag_writer(attr, *flags)
106
+ flags.each do |f|
107
+ ffi_attr_writer(f, simple: false) do |v|
108
+ v ? self[attr] += [f] : self[attr] -= [f]
109
+ v
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ def self.included(mod)
116
+ mod.extend(ClassMethods)
117
+ end
118
+
119
+ # Fill the native struct from another object or list of properties
120
+ # @param [Object] from
121
+ # for each attribute we call self.attr=(from.attr)
122
+ # @param [Hash<Symbol,Object>] args
123
+ # for each entry <attr,val> we call self.attr=(val)
124
+ # @return [self]
125
+ def fill(from = nil, **args)
126
+ if from.is_a?(Hash)
127
+ args.merge!(from)
128
+ else
129
+ self.class.ffi_attr_writers.each_key { |v| send("#{v}=", from.send(v)) if from.respond_to?(v) }
130
+ end
131
+ args.each_pair { |k, v| send("#{k}=", v) }
132
+ self
133
+ end
134
+
135
+ def inspect
136
+ "#{self.class.name} {#{self.class.ffi_attr_readers.keys.map { |r| "#{r}: #{send(r)} " }.join(',')}"
137
+ end
138
+
139
+ # Convert struct to hash
140
+ # @return [Hash<Symbol,Object>] map of reader attribute name to value
141
+ def to_h
142
+ self.class.ffi_attr_readers.keys.each_with_object({}) { |r, h| h[r] = send(r) }
143
+ end
144
+ end
145
+ end
data/lib/ffi/devt.rb ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ module FFI
6
+ # Calculate major/minor device numbers for use with mknod etc..
7
+ # @see makedev(3)
8
+ module Device
9
+ extend FFI::Library
10
+ ffi_lib FFI::Library::LIBC
11
+
12
+ prefix = FFI::Platform::IS_GNU ? 'gnu_dev_' : ''
13
+
14
+ # @!method makedev(major,minor)
15
+ # @param [Integer] major
16
+ # @param [Integer] minor
17
+ # @return [Integer] combined major/minor to a single value to pass to mknod etc
18
+ attach_function :makedev, "#{prefix}makedev".to_sym, %i[int int], :int
19
+
20
+ # @!method major(dev)
21
+ # @param [Integer] dev
22
+ # @return [Integer] the major component of dev
23
+ attach_function :major, "#{prefix}major".to_sym, [:int], :int
24
+
25
+ # @!method minor(dev)
26
+ # @param [Integer] dev
27
+ # @return [Integer] the minor component of dev
28
+ attach_function :minor, "#{prefix}minor".to_sym, [:int], :int
29
+ end
30
+ end
data/lib/ffi/flock.rb ADDED
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'accessors'
4
+
5
+ module FFI
6
+ # Flocking operation
7
+ class Flock < Struct
8
+ # Enum definitions associated with Flock
9
+ module Enums
10
+ extend FFI::Library
11
+ seek_whence = %i[seek_set seek_cur seek_end seek_data seek_hole]
12
+ SeekWhenceShort = enum :short, seek_whence
13
+ SeekWhence = enum :int, seek_whence
14
+
15
+ LockType = enum :short, %i[f_rdlck f_wrlck f_unlck]
16
+ LockCmd = enum :int, [:f_getlk, 5, :f_setlk, 6, :f_setlkw, 7]
17
+ end
18
+
19
+ include(Accessors)
20
+
21
+ layout(type: Enums::LockType, whence: Enums::SeekWhenceShort, start: :off_t, len: :off_t, pid: :pid_t)
22
+
23
+ ffi_attr_reader :type, :whence, :start, :len, :pid
24
+
25
+ # @!attribute [r] type
26
+ # @return [Symbol] lock type, :f_rdlck, :f_wrlck, :f_unlck
27
+
28
+ # @!attribute [r] whence
29
+ # @return [Symbol] specifies what the offset is relative to, one of :seek_set, :seek_cur or :seek_end
30
+ # corresponding to the whence argument to fseek(2) or lseek(2),
31
+
32
+ # @!attribute [r] start
33
+ # @return [Integer] the offset of the start of the region to which the lock applies, and is given in bytes
34
+ # relative to the point specified by #{whence} member.
35
+
36
+ # @!attribute [r] len
37
+ # @return [Integer] the length of the region to be locked.
38
+ #
39
+ # A value of 0 means the region extends to the end of the file.
40
+
41
+ # @!attribute [r] pid
42
+ # @return [Integer] the process ID (see Process Creation Concepts) of the process holding the lock.
43
+ # It is filled in by calling fcntl with the F_GETLK command, but is ignored when making a lock. If the
44
+ # conflicting lock is an open file description lock (see Open File Description Locks), then this field will be
45
+ # set to -1.
46
+ end
47
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ module FFI
6
+ # Support versioned functions until https://github.com/ffi/ffi/issues/889
7
+ #
8
+ # @example
9
+ # require 'ffi'
10
+ #
11
+ # module MyLibrary
12
+ # extend FFI::Library
13
+ #
14
+ # if FFI::Platform::IS_GNU
15
+ # require 'ffi/gnu_extensions'
16
+ #
17
+ # extend(FFI::GNUExtensions)
18
+ # # default versions for all functions
19
+ # ffi_lib_versions(%w[VERSION_X.3 VERSION_X.2])
20
+ # end
21
+ #
22
+ # attach_function :func_one, [:int], :int
23
+ # # override default with specific version
24
+ # attach_function :func_two, [], :int, versions ['VERSION_X.Y']
25
+ #
26
+ # end
27
+ module GNUExtensions
28
+ if FFI::Platform::IS_GNU
29
+ extend FFI::Library
30
+ ffi_lib 'libdl'
31
+
32
+ # @!method dlopen(library,type)
33
+ # @return [FFI::Pointer] library address, possibly NULL
34
+ attach_function :dlopen, %i[string int], :pointer
35
+ # @!method dlvsym(handle)
36
+ # @return [FFI::Pointer] function address, possibly NULL
37
+ attach_function :dlvsym, %i[pointer string string], :pointer
38
+ end
39
+
40
+ # @!visibility private
41
+ def self.extended(mod)
42
+ mod.extend(DLV) unless mod.respond_to?(:ffi_lib_versions)
43
+ end
44
+
45
+ # Override FFI::Library attach functions with support for dlvsym
46
+ module DLV
47
+ # Set the default version(s) for "{attach_function}" (GNU only)
48
+ # @param [Array<String>|String] versions the default list of versions to search
49
+ # @return [Array<String>|String]
50
+ def ffi_lib_versions(versions)
51
+ @versions = versions
52
+ end
53
+
54
+ # @!visibility private
55
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
56
+ def attach_function(name, func, args, returns = nil, options = nil)
57
+ versions = options.delete(:versions) if options.is_a?(Hash)
58
+
59
+ return super unless FFI::Platform::IS_GNU
60
+
61
+ # Hackety hack duplicated from FFI#attach_function
62
+ # rubocop:disable Style/ParallelAssignment, Style/TernaryParentheses, Layout/LineLength
63
+ mname, a2, a3, a4, a5 = name, func, args, returns, options
64
+ cname, arg_types, ret_type, opts = (a4 && (a2.is_a?(String) || a2.is_a?(Symbol))) ? [a2, a3, a4, a5] : [mname.to_s, a2, a3, a4]
65
+ # rubocop:enable Style/ParallelAssignment, Style/TernaryParentheses, Layout/LineLength
66
+
67
+ versions ||= @versions if defined?(@versions)
68
+ versions ||= []
69
+
70
+ return super if versions.empty?
71
+
72
+ function = versions.each do |v|
73
+ f = find_function_version(cname, v)
74
+ break f if f
75
+ end
76
+
77
+ # oh well, try the non-version function
78
+ return super unless function
79
+
80
+ arg_types = arg_types.map { |e| find_type(e) }
81
+ ret_type = find_type(ret_type)
82
+
83
+ invoker =
84
+ if arg_types.length.positive? && arg_types[arg_types.length - 1] == FFI::NativeType::VARARGS
85
+ VariadicInvoker.new(function, arg_types, ret_type, opts)
86
+ else
87
+ Function.new(ret_type, arg_types, function, opts)
88
+ end
89
+ invoker.attach(self, mname.to_s)
90
+ invoker
91
+ end
92
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
93
+
94
+ # use dlvsym to find a function address
95
+ # @param [String|Symbol] cname the function name
96
+ # @param [String] version the version name
97
+ # @return [FFI::Pointer] the function address
98
+ # @return [false] if the function/version combination does not exist in any library
99
+ def find_function_version(cname, version)
100
+ ffi_libraries.map(&:name).each do |l|
101
+ handle = GNUExtensions.dlopen(l, 1)
102
+ next if handle.null?
103
+
104
+ addr = GNUExtensions.dlvsym(handle, cname.to_s, version)
105
+
106
+ next if addr.null?
107
+
108
+ return addr
109
+ end
110
+
111
+ false
112
+ end
113
+ end
114
+ end
115
+ end