ffi-libfuse 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
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