ffi-libfuse 0.3.3 → 0.4.0

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -22
  3. data/README.md +1 -1
  4. data/lib/ffi/accessors.rb +21 -7
  5. data/lib/ffi/boolean_int.rb +1 -1
  6. data/lib/ffi/devt.rb +3 -3
  7. data/lib/ffi/libfuse/adapter/debug.rb +53 -15
  8. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +38 -21
  9. data/lib/ffi/libfuse/adapter/fuse3_support.rb +0 -1
  10. data/lib/ffi/libfuse/adapter/ruby.rb +210 -159
  11. data/lib/ffi/libfuse/adapter/safe.rb +69 -21
  12. data/lib/ffi/libfuse/callbacks.rb +2 -1
  13. data/lib/ffi/libfuse/filesystem/accounting.rb +1 -1
  14. data/lib/ffi/libfuse/filesystem/mapped_files.rb +33 -7
  15. data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +0 -1
  16. data/lib/ffi/libfuse/filesystem/virtual_dir.rb +293 -126
  17. data/lib/ffi/libfuse/filesystem/virtual_file.rb +85 -79
  18. data/lib/ffi/libfuse/filesystem/virtual_fs.rb +34 -15
  19. data/lib/ffi/libfuse/filesystem/virtual_link.rb +60 -0
  20. data/lib/ffi/libfuse/filesystem/virtual_node.rb +104 -87
  21. data/lib/ffi/libfuse/filesystem.rb +1 -1
  22. data/lib/ffi/libfuse/fuse2.rb +3 -2
  23. data/lib/ffi/libfuse/fuse3.rb +1 -1
  24. data/lib/ffi/libfuse/fuse_args.rb +5 -2
  25. data/lib/ffi/libfuse/fuse_buf.rb +112 -0
  26. data/lib/ffi/libfuse/fuse_buf_vec.rb +228 -0
  27. data/lib/ffi/libfuse/fuse_common.rb +10 -4
  28. data/lib/ffi/libfuse/fuse_config.rb +16 -7
  29. data/lib/ffi/libfuse/fuse_operations.rb +86 -41
  30. data/lib/ffi/libfuse/gem_helper.rb +91 -0
  31. data/lib/ffi/libfuse/gem_version.rb +4 -44
  32. data/lib/ffi/libfuse/io.rb +56 -0
  33. data/lib/ffi/libfuse/main.rb +27 -24
  34. data/lib/ffi/libfuse/test_helper.rb +68 -60
  35. data/lib/ffi/libfuse/version.rb +1 -1
  36. data/lib/ffi/libfuse.rb +1 -1
  37. data/lib/ffi/stat/native.rb +4 -4
  38. data/lib/ffi/stat.rb +19 -3
  39. data/lib/ffi/struct_array.rb +2 -1
  40. data/sample/hello_fs.rb +1 -1
  41. metadata +7 -3
  42. data/lib/ffi/libfuse/fuse_buffer.rb +0 -257
@@ -6,88 +6,94 @@ require 'stringio'
6
6
  module FFI
7
7
  module Libfuse
8
8
  module Filesystem
9
+ module Ruby
10
+ # Filesystem methods representing a single synthetic file at the root and satisfying
11
+ # Satisfies the contract of {Adapter::Ruby}
12
+ module VirtualFile
13
+ include VirtualNode
14
+
15
+ # @return [String] the (binary) content of the synthetic file
16
+ attr_reader :content
17
+
18
+ # @return [Integer] the number of links to this file
19
+ attr_reader :nlink
20
+
21
+ # Create an empty synthetic file
22
+ def initialize(accounting: nil)
23
+ super(accounting: accounting)
24
+ end
25
+
26
+ # @!group FUSE Callbacks
27
+
28
+ def getattr(path, stat = nil, ffi = nil)
29
+ # We don't exist until create or otherwise or virtual stat exists
30
+ raise Errno::ENOENT unless root?(path) && virtual_stat
31
+
32
+ stat&.file(size: (ffi&.fh || content).size, nlink: nlink, **virtual_stat)
33
+ self
34
+ end
35
+
36
+ # @param [String] _path ignored, expected to be '/'
37
+ # @param [Integer] mode
38
+ # @param [FuseFileInfo] ffi
39
+ # @return [Object] a file handled (captured by {Adapter::Ruby::Prepend})
40
+ def create(_path, mode, ffi = nil)
41
+ init_node(mode)
42
+ @content = String.new(encoding: 'binary')
43
+ @nlink = 1
44
+ sio(ffi) if ffi
45
+ end
46
+
47
+ def open(_path, ffi)
48
+ virtual_stat[:atime] = Time.now.utc
49
+ sio(ffi)
50
+ end
51
+
52
+ # write(const char* path, char *buf, size_t size, off_t offset, struct fuse_file_info* fi)
53
+ def write(path, data, offset = 0, _ffi = nil)
54
+ raise Errno::ENOENT unless root?(path)
55
+
56
+ accounting&.write(content.size, data.size, offset)
57
+ virtual_stat[:mtime] = Time.now.utc
58
+ nil # just let the sio in ffi handle it
59
+ end
60
+
61
+ def truncate(path, size, ffi = nil)
62
+ raise Errno::ENOENT unless root?(path)
63
+
64
+ accounting&.truncate(content.size, size)
65
+ sio(ffi).truncate(size)
66
+ virtual_stat[:mtime] = Time.now.utc
67
+ end
68
+
69
+ def link(_target, path)
70
+ raise Errno::ENOENT unless root?(path)
71
+
72
+ accounting&.adjust(content.size, 1) if @nlink.zero?
73
+ @nlink += 1
74
+ self
75
+ end
76
+
77
+ def unlink(path)
78
+ raise Errno::ENOENT unless root?(path)
79
+
80
+ @nlink -= 1
81
+ accounting&.adjust(-content.size, -1) if @nlink.zero?
82
+ end
83
+
84
+ private
85
+
86
+ def sio(ffi)
87
+ ffi&.fh || StringIO.new(content, ffi&.flags)
88
+ end
89
+ end
90
+ end
91
+
9
92
  # A Filesystem representing a single synthetic file at the root
10
- class VirtualFile < VirtualNode
93
+ class VirtualFile
11
94
  prepend Adapter::Ruby::Prepend
12
95
  include Fuse2Compat
13
-
14
- # @return [String] the (binary) content of the synthetic file
15
- attr_reader :content
16
-
17
- # Create an empty synthetic file
18
- def initialize(accounting: nil)
19
- super(accounting: accounting)
20
- end
21
-
22
- # @!visibility private
23
- def path_method(_method, *_args)
24
- raise Errno::ENOENT
25
- end
26
-
27
- # @!group FUSE Callbacks
28
-
29
- def getattr(path, stat, ffi = nil)
30
- # We don't exist until create or otherwise or virtual stat exists
31
- raise Errno::ENOENT unless root?(path) && virtual_stat
32
-
33
- stat.file(size: (ffi&.fh || content).size, **virtual_stat)
34
- self
35
- end
36
-
37
- # @param [String] _path ignored, expected to be '/'
38
- # @param [Integer] mode
39
- # @param [FuseFileInfo] ffi
40
- # @return [Object] a file handled (captured by {Adapter::Ruby::Prepend})
41
- def create(_path, mode, ffi = nil)
42
- init_node(mode)
43
- @content = String.new(encoding: 'binary')
44
- sio(ffi) if ffi
45
- end
46
-
47
- def open(_path, ffi)
48
- virtual_stat[:atime] = Time.now.utc
49
- sio(ffi)
50
- end
51
-
52
- # op[:read] = [:pointer, :size_t, :off_t, FuseFileInfo.by_ref]
53
- def read(path, size, off, ffi)
54
- raise Errno::ENOENT unless root?(path)
55
-
56
- io = sio(ffi)
57
- io.seek(off)
58
- io.read(size)
59
- end
60
-
61
- # write(const char* path, char *buf, size_t size, off_t offset, struct fuse_file_info* fi)
62
- def write(path, data, offset = 0, ffi = nil)
63
- raise Errno::ENOENT unless root?(path)
64
-
65
- accounting&.write(content.size, data.size, offset)
66
- io = sio(ffi)
67
- io.seek(offset)
68
- io.write(data)
69
- virtual_stat[:mtime] = Time.now.utc
70
- end
71
-
72
- def truncate(path, size, ffi = nil)
73
- raise Errno::ENOENT unless root?(path)
74
-
75
- accounting&.truncate(content.size, size)
76
- sio(ffi).truncate(size)
77
- virtual_stat[:mtime] = Time.now.utc
78
- end
79
-
80
- def unlink(path)
81
- raise Errno::ENOENT unless root?(path)
82
-
83
- accounting&.adjust(-content.size, -1)
84
- end
85
-
86
- private
87
-
88
- def sio(ffi)
89
- ffi&.fh || StringIO.new(content, ffi&.flags)
90
- end
96
+ include Ruby::VirtualFile
91
97
  end
92
98
  end
93
99
  end
@@ -15,6 +15,8 @@ module FFI
15
15
  # Delegate filesystems like {VirtualDir} may raise ENOTSUP to indicate a callback is not handled at runtime
16
16
  # although the behaviour of C libfuse varies in this regard.
17
17
  #
18
+ # It is writable to the user that mounted it may create and edit files within it
19
+ #
18
20
  # Filesystem options
19
21
  # ===
20
22
  #
@@ -33,15 +35,14 @@ module FFI
33
35
  # Note that {VirtualFile} and {MappedFiles} both prepend {Adapter::Ruby::Prepend} which implements
34
36
  # the logic to fallback from :read/:write_buf to plain :read/:write as necessary to support this option.
35
37
  #
36
- # It is writable to the user that mounted it may create and edit files within it
37
- #
38
38
  # @example
39
39
  # class MyFS < FFI::Libfuse::Filesystem::VirtualFS
40
40
  # def fuse_configure
41
41
  # build({ 'hello' => { 'world.txt' => 'Hello World'}})
42
42
  # mkdir("/hello")
43
- # create("/hello/world").write("Hello World!\n")
43
+ # create("/hello/world.txt").write("Hello World!\n")
44
44
  # create("/hello/everybody").write("Hello Everyone!\n")
45
+ # symlink("/hello/link","everybody")`
45
46
  # end
46
47
  # end
47
48
  #
@@ -51,7 +52,7 @@ module FFI
51
52
  include Utils
52
53
  include Adapter::Context
53
54
  include Adapter::Debug
54
- include Adapter::Safe
55
+ include Adapter::Fuse2Compat
55
56
 
56
57
  # @return [Object] the root filesystem that quacks like a {FuseOperations}
57
58
  attr_reader :root
@@ -73,12 +74,13 @@ module FFI
73
74
 
74
75
  # @overload build(files)
75
76
  # Adds files directly to the filesystem
76
- # @param [Hash] files map of paths to content responding to
77
+ # @param [Hash<String,:each_pair, :readdir,:getattr .:to_str] files map of paths to content generated
78
+ # according to the content implementing one of the methods below
77
79
  #
78
80
  # * :each_pair is treated as a subdir of files
79
81
  # * :readdir (eg {PassThroughDir}) is treated as a directory- sent via mkdir
80
82
  # * :getattr (eg {PassThroughFile}) is treated as a file - sent via create
81
- # * :to_str (eg {::String} ) is created as a {VirtualFile}
83
+ # * :to_str (eg {::String} ) is created a default file, with the string sent via write
82
84
  def build(files, base_path = Pathname.new('/'))
83
85
  files.each_pair do |path, content|
84
86
  path = (base_path + path).cleanpath
@@ -103,10 +105,6 @@ module FFI
103
105
  # * :copy_file_range can raise ENOTSUP to trigger glibc to fallback to inefficient copy
104
106
  def fuse_respond_to?(method)
105
107
  case method
106
- when :getdir, :fgetattr
107
- # TODO: Find out if fgetattr works on linux, something wrong with stat values on OSX.
108
- # https://github.com/osxfuse/osxfuse/issues/887
109
- false
110
108
  when :read_buf, :write_buf
111
109
  !no_buf
112
110
  else
@@ -117,16 +115,21 @@ module FFI
117
115
  # Default fuse options
118
116
  # Subclasses can override this method and call super with the additional options:
119
117
  # @param [Hash] opts additional options to parse into the {#options} attribute
120
- def fuse_options(args, opts = {})
118
+ # @yield(key, value, **args)
119
+ # Called for each matching key in opts
120
+ # @see FuseArgs#parse!
121
+ def fuse_options(args, opts = {}, &block)
121
122
  @options = {}
122
123
  opts = opts.merge({ 'no_buf' => :no_buf }).merge(Accounting::OPTIONS)
123
- args.parse!(opts) do |key:, value:, **|
124
+ args.parse!(opts) do |key:, value:, **kwargs|
124
125
  case key
125
126
  when *Accounting::OPTIONS.values.uniq
126
127
  next accounting.fuse_opt_proc(key: key, value: value)
127
128
  when :no_buf
128
129
  @no_buf = true
129
130
  else
131
+ next block.call(key, value, **kwargs) if block
132
+
130
133
  options[key] = value
131
134
  end
132
135
  :handled
@@ -147,6 +150,21 @@ module FFI
147
150
  self.class.name
148
151
  end
149
152
 
153
+ # @!visibility private
154
+ def init_fuse_config(fuse_config, _fuse_version)
155
+ fuse_config.use_ino = use_ino
156
+ end
157
+
158
+ # Configure whether entries in this filesystem provide useful inode values in #gettattr and #readdir
159
+ #
160
+ # Defaults to true since default Dir, File, Link all use {VirtualNode} which uses
161
+ # Ruby object id as the inode value.
162
+ #
163
+ # Subclasses should override to false if some sub-filesystems will not provide inode values.
164
+ # @return [Boolean]
165
+ def use_ino
166
+ true
167
+ end
150
168
  # @!endgroup
151
169
 
152
170
  private
@@ -156,15 +174,16 @@ module FFI
156
174
  end
157
175
 
158
176
  def build_readdir(content, path)
159
- @root.mkdir(path.to_s) { content }
177
+ @root.mkdir(path.to_s) { |_| content }
160
178
  end
161
179
 
162
180
  def build_getattr(content, path)
163
- @root.create(path.to_s) { content }
181
+ @root.create(path.to_s) { |_| content }
164
182
  end
165
183
 
166
184
  def build_to_str(content, path)
167
- @root.create(path.to_s) { content }
185
+ vf = @root.create(path.to_s)
186
+ vf.write(content.to_str)
168
187
  end
169
188
 
170
189
  # Passes FUSE Callbacks on to the {#root} filesystem
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFI
4
+ module Libfuse
5
+ module Filesystem
6
+ module Ruby
7
+ # Filesystem methods representing a symbolic link
8
+ # Satisfies the contract of {Adapter::Ruby}
9
+ module VirtualLink
10
+ attr_accessor :target
11
+
12
+ include VirtualNode
13
+ def initialize(accounting: nil)
14
+ @target = target
15
+ super(accounting: accounting)
16
+ end
17
+
18
+ def readlink(_path, size)
19
+ @target[0, size - 1] # buffer size needs null terminator
20
+ end
21
+
22
+ def symlink(from_path, path)
23
+ raise Errno::ENOENT unless root?(path)
24
+
25
+ @target = from_path
26
+ init_node(0o777)
27
+ end
28
+
29
+ def link(_from_path, path)
30
+ raise Errno::ENOENT unless root?(path)
31
+
32
+ # Cannot hard link a symbolic link
33
+ raise Errno::EPERM
34
+ end
35
+
36
+ def unlink(path)
37
+ raise Errno::ENOENT unless root?(path)
38
+
39
+ accounting&.adjust(0, -1)
40
+ end
41
+
42
+ def getattr(path, stat = nil, _ffi = nil)
43
+ # We don't exist until create or otherwise or virtual stat exists
44
+ raise Errno::ENOENT unless root?(path) && virtual_stat
45
+
46
+ stat&.symlink(size: @target.length + 1, **virtual_stat)
47
+ self
48
+ end
49
+ end
50
+ end
51
+
52
+ # A Filesystem that represents a single symbolic link at the root
53
+ class VirtualLink
54
+ prepend Adapter::Ruby::Prepend
55
+ include Fuse2Compat
56
+ include Ruby::VirtualLink
57
+ end
58
+ end
59
+ end
60
+ end
@@ -6,95 +6,112 @@ require_relative '../adapter/ruby'
6
6
  module FFI
7
7
  module Libfuse
8
8
  module Filesystem
9
- # @abstract
10
- # Common FUSE Callbacks for a virtual inode
11
- #
12
- # **Note** this class is used by both {VirtualFile} which is under {Adapter::Ruby::Prepend}
13
- # and {VirtualDir} which passes on native {FuseOperations} calls
14
- class VirtualNode
15
- # @return [Hash<Symbol,Integer>] base file or directory stat information used for :getattr of this node
16
- attr_reader :virtual_stat
17
-
18
- # @return [Hash<String,String>] virtual extended attributes
19
- attr_reader :virtual_xattr
20
-
21
- # @return [Accounting|nil] file system statistcs accumulator
22
- attr_reader :accounting
23
-
24
- # @param [Accounting] accounting accumulator of filesystem statistics
25
- def initialize(accounting: Accounting.new)
26
- @accounting = accounting
27
-
28
- @virtual_xattr = {}
29
- end
30
-
31
- # @!method path_method(callback, *args)
32
- # @abstract
33
- # called if this node cannot handle the callback (ie path is not root or an entry in this directory)
34
-
35
- # @!group FUSE Callbacks
36
-
37
- def utimens(path, *args)
38
- return path_method(__method__, path, *args) unless root?(path)
39
-
40
- atime, mtime, *_fuse3 = args
41
- # if native fuse call atime will be Array<Stat::TimeSpec>
42
- atime, mtime = Stat::TimeSpec.fill_times(atime[0, 2], 2).map(&:time) if atime.is_a?(Array)
43
- virtual_stat[:atime] = atime if atime
44
- virtual_stat[:mtime] = mtime if mtime
45
- virtual_stat[:ctime] = mtime if mtime
46
- end
47
-
48
- def chmod(path, mode, *args)
49
- return path_method(__method__, path, mode, *args) unless root?(path)
50
-
51
- virtual_stat[:mode] = mode
52
- virtual_stat[:ctime] = Time.now
53
- end
54
-
55
- def chown(path, uid, gid, *args)
56
- return path_method(__method__, path, uid, gid, *args) unless root?(path)
57
-
58
- virtual_stat[:uid] = uid
59
- virtual_stat[:gid] = gid
60
- virtual_stat[:ctime] = Time.now
9
+ module Ruby
10
+ # Common FUSE Callbacks for a virtual inode representing a single filesystem object at '/'
11
+ #
12
+ # **Note** this module is used by both {VirtualFile} which is under {Adapter::Ruby::Prepend}
13
+ # and {VirtualDir} which passes on native {FuseOperations} calls
14
+ module VirtualNode
15
+ # @return [Hash<Symbol,Integer>] base file or directory stat information used for :getattr of this node
16
+ attr_reader :virtual_stat
17
+
18
+ # @return [Hash<String,String>] virtual extended attributes
19
+ attr_reader :virtual_xattr
20
+
21
+ # @return [Accounting|nil] file system statistcs accumulator
22
+ attr_reader :accounting
23
+
24
+ # @param [Accounting] accounting accumulator of filesystem statistics
25
+ def initialize(accounting: Accounting.new)
26
+ @accounting = accounting
27
+
28
+ @virtual_xattr = {}
29
+ end
30
+
31
+ # @!method path_method(callback, *args)
32
+ # @abstract
33
+ # called if this node cannot handle the callback (ie path is not root or an entry in this directory)
34
+
35
+ # @!group FUSE Callbacks
36
+
37
+ def utimens(path, *args)
38
+ return path_method(__method__, path, *args) unless root?(path)
39
+
40
+ atime, mtime, *_fuse3 = args
41
+ # if native fuse call atime will be Array<Stat::TimeSpec>
42
+ atime, mtime = Stat::TimeSpec.fill_times(atime[0, 2], 2).map(&:time) if atime.is_a?(Array)
43
+ virtual_stat[:atime] = atime if atime
44
+ virtual_stat[:mtime] = mtime if mtime
45
+ virtual_stat[:ctime] = mtime if mtime
46
+ end
47
+
48
+ def chmod(path, mode, *args)
49
+ return path_method(__method__, path, mode, *args) unless root?(path)
50
+
51
+ virtual_stat[:mode] = mode
52
+ virtual_stat[:ctime] = Time.now
53
+ end
54
+
55
+ def chown(path, uid, gid, *args)
56
+ return path_method(__method__, path, uid, gid, *args) unless root?(path)
57
+
58
+ virtual_stat[:uid] = uid
59
+ virtual_stat[:gid] = gid
60
+ virtual_stat[:ctime] = Time.now
61
+ end
62
+
63
+ def statfs(path, statfs_buf)
64
+ return path_method(__method__, path, statfs_buf) unless root?(path)
65
+ raise Errno::ENOTSUP unless accounting
66
+
67
+ accounting.to_statvfs(statfs_buf)
68
+ end
69
+
70
+ def getxattr(path, name, buf = nil, size = nil)
71
+ return path_method(__method__, path, name, buf, size) unless root?(path)
72
+ return virtual_xattr[name] unless buf
73
+
74
+ Adapter::Ruby.getxattr(buf, size) { virtual_xattr[name] }
75
+ end
76
+
77
+ def listxattr(path, buf = nil, size = nil)
78
+ return path_method(__method__, path) unless root?(path)
79
+ return virtual_xattr.keys unless buf
80
+
81
+ Adapter::Ruby.listxattr(buf, size) { virtual_xattr.keys }
82
+ end
83
+
84
+ # @!endgroup
85
+
86
+ # Initialise the stat information for the node - should only be called once (eg from create or mkdir)
87
+ def init_node(mode, ctx: FuseContext.get, now: Time.now)
88
+ @virtual_stat =
89
+ {
90
+ mode: mode & ~ctx.umask, uid: ctx.uid, gid: ctx.gid,
91
+ ctime: now, mtime: now, atime: now,
92
+ ino: object_id
93
+ }
94
+ accounting&.adjust(0, +1)
95
+ self
96
+ end
97
+
98
+ private
99
+
100
+ # @!visibility private
101
+ def path_method(_method, *_args)
102
+ raise Errno::ENOENT
103
+ end
104
+
105
+ def root?(path)
106
+ path.to_s == '/'
107
+ end
61
108
  end
109
+ end
62
110
 
63
- def statfs(path, statfs_buf)
64
- return path_method(__method__, path, statfs_buf) unless root?(path)
65
- raise Errno::ENOTSUP unless accounting
66
-
67
- accounting.to_statvfs(statfs_buf)
68
- end
69
-
70
- def getxattr(path, name, buf = nil, size = nil)
71
- return path_method(__method__, path, name, buf, size) unless root?(path)
72
- return virtual_xattr[name] unless buf
73
-
74
- Adapter::Ruby.getxattr(buf, size) { virtual_xattr[name] }
75
- end
76
-
77
- def listxattr(path, buf = nil, size = nil)
78
- return path_method(__method__, path) unless root?(path)
79
- return virtual_xattr.keys unless buf
80
-
81
- Adapter::Ruby.listxattr(buf, size) { virtual_xattr.keys }
82
- end
83
-
84
- # @!endgroup
85
-
86
- # Initialise the stat information for the node - should only be called once (eg from create or mkdir)
87
- def init_node(mode, ctx: FuseContext.get, now: Time.now)
88
- @virtual_stat = { mode: mode & ~ctx.umask, uid: ctx.uid, gid: ctx.gid, ctime: now, mtime: now, atime: now }
89
- accounting&.adjust(0, +1)
90
- self
91
- end
92
-
93
- private
94
-
95
- def root?(path)
96
- path.to_s == '/'
97
- end
111
+ # @abstract
112
+ # Base class Represents a virtual inode
113
+ class VirtualNode
114
+ include Ruby::VirtualNode
98
115
  end
99
116
  end
100
117
  end
@@ -7,7 +7,7 @@ module FFI
7
7
  # This module namespace contains classes and modules to assist with building and composing filesystems
8
8
  #
9
9
  # ### Virtual Filesystems
10
- # Classes to help compose in-memory filesystems {VirtualFS}, {VirtualDir}, {VirtualFile}
10
+ # Classes to help compose in-memory filesystems {VirtualFS}, {VirtualDir}, {VirtualFile}, {VirtualLink}
11
11
  #
12
12
  # ### Mapped Filesystem
13
13
  # Modules to map paths in the fuse filesystem to either real files or other filesystem objects
@@ -44,14 +44,15 @@ module FFI
44
44
  def parse_cmdline(args, handler: nil)
45
45
  # This also handles -h to print help information on stderr
46
46
  # Parse mountpoint, -f , -s from args
47
- # @return [Array<(String,Boolean,Boolean)>|nil]
47
+ # @return [Array<(String,Boolean,Boolean)>]
48
48
  # mountpoint, multi_thread, foreground options from args if available
49
49
  # nil if no mountpoint, or options is requesting help or version information
50
50
  mountpoint_ptr = FFI::MemoryPointer.new(:pointer, 1)
51
51
  multi_thread_ptr = FFI::MemoryPointer.new(:int, 1)
52
52
  foreground_ptr = FFI::MemoryPointer.new(:int, 1)
53
53
 
54
- return nil unless Libfuse.fuse_parse_cmdline2(args, mountpoint_ptr, multi_thread_ptr, foreground_ptr).zero?
54
+ res = Libfuse.fuse_parse_cmdline2(args, mountpoint_ptr, multi_thread_ptr, foreground_ptr)
55
+ raise Error unless res.zero?
55
56
 
56
57
  # noinspection RubyResolve
57
58
  mp_data_ptr = mountpoint_ptr.get_pointer(0)
@@ -56,7 +56,7 @@ module FFI
56
56
  class << self
57
57
  def parse_cmdline(args, handler: nil)
58
58
  cmdline_opts = FuseCmdlineOpts.new
59
- return nil unless Libfuse.fuse_parse_cmdline3(args, cmdline_opts).zero?
59
+ raise Error unless Libfuse.fuse_parse_cmdline3(args, cmdline_opts).zero?
60
60
 
61
61
  handler&.fuse_debug(cmdline_opts.debug) if handler.respond_to?(:fuse_debug)
62
62
 
@@ -121,7 +121,8 @@ module FFI
121
121
  # - :error an error, alternatively raise {Error}
122
122
  # - :keep retain the current argument for further processing
123
123
  # - :handled,:discard remove the current argument from further processing
124
- # @return [nil|self] nil on error otherwise self
124
+ # @raise Error if an error is raised during parsing
125
+ # @return [self]
125
126
  def parse!(opts, data = nil, ignore: %i[non_option unmatched], &block)
126
127
  ignore ||= []
127
128
 
@@ -140,7 +141,9 @@ module FFI
140
141
  end
141
142
 
142
143
  fop = fuse_opt_proc(symbols, bool_opts, param_opts, ignore, &block)
143
- Libfuse.fuse_opt_parse(self, data, int_opts, fop).zero? ? self : nil
144
+ raise Error unless Libfuse.fuse_opt_parse(self, data, int_opts, fop).zero?
145
+
146
+ self
144
147
  end
145
148
 
146
149
  private