ffi-libfuse 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  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 +2 -9
  31. data/lib/ffi/libfuse/io.rb +56 -0
  32. data/lib/ffi/libfuse/main.rb +27 -24
  33. data/lib/ffi/libfuse/test_helper.rb +68 -60
  34. data/lib/ffi/libfuse/version.rb +1 -1
  35. data/lib/ffi/libfuse.rb +1 -1
  36. data/lib/ffi/stat/native.rb +4 -4
  37. data/lib/ffi/stat.rb +19 -3
  38. data/lib/ffi/struct_array.rb +2 -1
  39. data/sample/hello_fs.rb +1 -1
  40. metadata +6 -3
  41. 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