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
@@ -5,13 +5,6 @@ module FFI
5
5
  module Libfuse
6
6
  # @!visibility private
7
7
  class GemHelper
8
- SEMVER_TAG_REGEX = /^v\d+\.\d+\.\d+/.freeze
9
-
10
- # branches the format is refs/heads/<branch_name>,
11
- # tags it is refs/tags/<tag_name>.
12
- # for pull requests it is refs/pull/<pr_number>/merge,
13
- GIT_REF_TYPES = { 'heads' => :branch, 'tags' => :tag, 'pull' => :pull }.freeze
14
-
15
8
  class << self
16
9
  # set when install'd.
17
10
  attr_accessor :instance
@@ -33,7 +26,7 @@ module FFI
33
26
  return [ref, nil] unless ref&.start_with?('refs/')
34
27
 
35
28
  _refs, ref_type, ref_name = ref.split('/', 3)
36
- [ref_name, GIT_REF_TYPES[ref_type]]
29
+ [ref_name, { 'heads' => :branch, 'tags' => :tag, 'pull' => :pull }[ref_type]]
37
30
  end
38
31
 
39
32
  def gem_version(main_branch:, version:, env: ENV)
@@ -44,7 +37,7 @@ module FFI
44
37
  when :branch
45
38
  ref_name == main_branch ? [version] : [version, ref_name]
46
39
  when :tag
47
- SEMVER_TAG_REGEX.match?(ref_name) ? [ref_name[1..]] : [version, ref_name]
40
+ /^v\d+\.\d+\.\d+/.match?(ref_name) ? [ref_name[1..]] : [version, ref_name]
48
41
  when :pull
49
42
  pr_number, merge, _rest = ref_name.split('/')
50
43
  # GITHUB_BASE_REF The name of the base ref or target branch of the pull request in a workflow run
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFI
4
+ module Libfuse
5
+ # Helpers for reading/writing to io like objects
6
+ module IO
7
+ class << self
8
+ # Helper to convert a ruby object to size bytes required by {FuseOperations#read}
9
+ # @param [#pread, #seek & #read, #to_s] io the source to read from, either...
10
+ #
11
+ # * an {::IO} like object via :pread(size, offset) or :seek(offset) then :read(size)
12
+ # * or the String from :to_s
13
+ #
14
+ # @param [Integer] size
15
+ # @param [Integer, nil] offset
16
+ # if nil the io is assumed to be already positioned
17
+ # @return [String] the extracted data
18
+ def read(io, size, offset = nil)
19
+ return io.pread(size, offset) if offset && io.respond_to?(:pread)
20
+
21
+ if (offset ? %i[seek read] : %i[read]).all? { |m| io.respond_to?(m) }
22
+ io.seek(offset) if offset
23
+ return io.read(size)
24
+ end
25
+
26
+ io.to_s[offset || 0, size] || ''
27
+ end
28
+
29
+ # Helper to write date to {::IO} or {::String} like objects for use with #{FuseOperations#write}
30
+ # @param [#pwrite, #seek & #write, #[]=] io an object that accepts String data via...
31
+ #
32
+ # * ```ruby io.pwrite(data, offset)```
33
+ # * ```ruby io.seek(offset) ; io.write(data)```
34
+ # * ```ruby io[offset, data.size] = data```
35
+ # @param [String] data
36
+ # @param [nil, Integer] offset
37
+ # if not nil start start writing at this position in io
38
+ # @return [Integer] number of bytes written
39
+ # @raise [Errno::EBADF] if io does not support the requisite methods
40
+ def write(io, data, offset = nil)
41
+ if offset && io.respond_to?(:pwrite)
42
+ io.pwrite(data, offset)
43
+ elsif (offset ? %i[seek write] : %i[write]).all? { |m| io.respond_to?(m) }
44
+ io.seek(offset) if offset
45
+ io.write(data)
46
+ elsif io.respond_to?(:[]=) # eg String
47
+ io[offset || 0, data.size] = data
48
+ data.size
49
+ else
50
+ raise "cannot :pwrite or :write to #{io}", Errno::EBADF
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -24,15 +24,13 @@ module FFI
24
24
 
25
25
  # Main function of FUSE
26
26
  #
27
- # This function:
28
- #
29
27
  # - parses command line options - see {fuse_parse_cmdline}
30
- # exiting immediately if help or version options were processed
31
- # - calls {#fuse_debug}, {#fuse_options}, {#fuse_configure} if implemented by operations
32
- # - installs signal handlers for INT, HUP, TERM to unmount and exit filesystem
33
- # - installs custom signal handlers if operations implements {fuse_traps}
34
- # - creates a fuse handle mounted with registered operations - see {fuse_create}
35
- # - calls either the single-threaded (option -s) or the multi-threaded event loop - see {FuseCommon#run}
28
+ # - calls {#fuse_configure} if implemented by operations
29
+ # - creates a fuse handle see {fuse_create}
30
+ # - returns 0 if help or version options were processed (ie after all messages have been printed by libfuse)
31
+ # - returns 2 if fuse handle is not successfully mounted
32
+ # - calls {#fuse_traps} if implemented by operations
33
+ # - calls run on the fuse handle with options from previous steps- see {FuseCommon#run}
36
34
  #
37
35
  # @param [Array<String>] argv mount.fuse arguments
38
36
  # expects progname, mountpoint, options....
@@ -46,23 +44,23 @@ module FFI
46
44
  # @return [Integer] suitable for process exit code
47
45
  def fuse_main(*argv, operations:, args: argv.any? ? argv : default_args, private_data: nil)
48
46
  run_args = fuse_parse_cmdline(args: args, handler: operations)
49
- return 2 unless run_args
50
47
 
51
48
  fuse_args = run_args.delete(:args)
52
49
  mountpoint = run_args.delete(:mountpoint)
53
50
 
54
- return 3 unless fuse_configure(operations: operations, **run_args)
51
+ show_only = run_args[:show_help] || run_args[:show_version]
52
+
53
+ return 3 if !show_only && !fuse_configure(operations)
55
54
 
56
55
  warn "FuseCreate: mountpoint: #{mountpoint}, args: [#{fuse_args.argv.join(' ')}]" if run_args[:debug]
57
56
  warn "FuseRun: #{run_args}" if run_args[:debug]
58
57
 
59
58
  fuse = fuse_create(mountpoint, args: fuse_args, operations: operations, private_data: private_data)
60
59
 
61
- return 0 if run_args[:show_help] || run_args[:show_version]
60
+ return 0 if show_only
62
61
  return 2 if !fuse || !mountpoint
63
62
 
64
- return unless fuse
65
-
63
+ run_args[:traps] = operations.fuse_traps if operations.respond_to?(:fuse_traps)
66
64
  fuse.run(**run_args)
67
65
  end
68
66
  alias main fuse_main
@@ -72,7 +70,6 @@ module FFI
72
70
  # - parses standard command line options (-d -s -h -V)
73
71
  # will call {fuse_debug}, {fuse_version}, {fuse_help} if implemented by handler
74
72
  # - calls {fuse_options} for custom option processing if implemented by handler
75
- # - records signal handlers if operations implements {fuse_traps}
76
73
  # - parses standard fuse mount options
77
74
  #
78
75
  # @param [Array<String>] argv mount.fuse arguments
@@ -88,24 +85,25 @@ module FFI
88
85
  # * show_version [Boolean]: -v or --version
89
86
  # * debug [Boolean]: -d
90
87
  # * others are options to pass to {FuseCommon#run}
88
+ # @return [nil] if args are not parsed successfully
91
89
  def fuse_parse_cmdline(*argv, args: argv.any? ? argv : default_args, handler: nil)
92
90
  args = fuse_init_args(args)
93
91
 
94
92
  # Parse args and print cmdline help
95
93
  run_args = Fuse.parse_cmdline(args, handler: handler)
96
- return nil unless run_args
97
94
 
98
- return nil if handler.respond_to?(:fuse_options) && !handler.fuse_options(args)
95
+ handler.fuse_options(args) if handler.respond_to?(:fuse_options)
99
96
 
100
- run_args[:traps] = handler.fuse_traps if handler.respond_to?(:fuse_traps)
101
-
102
- return nil unless parse_run_options(args, run_args)
97
+ parse_run_options(args, run_args)
103
98
 
104
99
  run_args[:args] = args
105
100
  run_args
101
+ rescue Error
102
+ nil
106
103
  end
107
104
 
108
- # @return [FuseCommon|nil] the mounted filesystem or nil if not mounted
105
+ # @return [FuseCommon] the mounted filesystem handle
106
+ # @return [nil] if not mounted (eg due to --help or --version, or an error)
109
107
  def fuse_create(mountpoint, *argv, operations:, args: nil, private_data: nil)
110
108
  args = fuse_init_args(args || argv)
111
109
 
@@ -116,8 +114,8 @@ module FFI
116
114
  end
117
115
 
118
116
  # @!visibility private
119
- def fuse_configure(operations:, show_help: false, show_version: false, **_)
120
- return true unless operations.respond_to?(:fuse_configure) && !show_help && !show_version
117
+ def fuse_configure(operations)
118
+ return true unless operations.respond_to?(:fuse_configure)
121
119
 
122
120
  # Provide sensible values for FuseContext in case this is referenced during configure
123
121
  FFI::Libfuse::FuseContext.overrides do
@@ -166,7 +164,8 @@ module FFI
166
164
  # @abstract
167
165
  # Called to allow filesystem to handle custom options and observe standard mount options #
168
166
  # @param [FuseArgs] args
169
- # @return [Boolean] true if args parsed successfully
167
+ # @raise [Error] if there is an error parsing the options
168
+ # @return [void]
170
169
  # @see FuseArgs#parse!
171
170
  # @example
172
171
  # OPTIONS = { 'config=' => :config, '-c ' => :config }
@@ -174,7 +173,7 @@ module FFI
174
173
  # args.parse!(OPTIONS) do |key:, value:, out:, **opts|
175
174
  #
176
175
  # # raise errors for invalid config
177
- # raise FFI::Libfuse::FuseArgs::Error, "Invalid config" unless valid_config?(key,value)
176
+ # raise FFI::Libfuse::Error, "Invalid config" unless valid_config?(key,value)
178
177
  #
179
178
  # # Configure the file system
180
179
  # @config = value if key == :config
@@ -189,6 +188,8 @@ module FFI
189
188
 
190
189
  # @!method fuse_traps
191
190
  # @abstract
191
+ # Passed to {FuseCommon#run} to allow filesystem to handle custom signal traps. These traps
192
+ # are merged over those from {FuseCommon#default_traps}
192
193
  # @return [Hash<String|Symbol|Integer,String|Proc>]
193
194
  # map of signal name or number to signal handler as per Signal.trap
194
195
  # @example
@@ -198,6 +199,7 @@ module FFI
198
199
 
199
200
  # @!method fuse_version
200
201
  # @abstract
202
+ # Called as part of generating output for the -V option
201
203
  # @return [String] a custom version string to output with -V option
202
204
 
203
205
  # @!method fuse_help
@@ -214,6 +216,7 @@ module FFI
214
216
  # @!method fuse_configure
215
217
  # @abstract
216
218
  # Called immediately before the filesystem is mounted, after options have been parsed
219
+ # (eg to validate required options)
217
220
  #
218
221
  # @raise [Error] to prevent the mount from proceeding
219
222
  # @return [void]
@@ -9,9 +9,6 @@ module FFI
9
9
  module Libfuse
10
10
  # Can be included test classes to assist with running/debugging filesystems
11
11
  module TestHelper
12
- # rubocop:disable Metrics/AbcSize
13
- # rubocop:disable Metrics/MethodLength
14
-
15
12
  # Runs the fuse loop on a pre configured fuse filesystem
16
13
  # @param [FuseOperations] operations
17
14
  # @param [Array<String>] args to pass to {FFI::Libfuse::Main.fuse_create}
@@ -19,7 +16,7 @@ module FFI
19
16
  # @yield [mnt]
20
17
  # caller can execute and test file operations using mnt and ruby File/Dir etc
21
18
  # the block is run in a forked process and is successful unless an exception is raised
22
- # @yieldparam [String] mnt the temporary direct used as the mount point
19
+ # @yieldparam [String] mnt the temporary directory used as the mount point
23
20
  # @raise [Error] if unexpected state is found during operations
24
21
  # @return [void]
25
22
  def with_fuse(operations, *args, **options)
@@ -34,34 +31,14 @@ module FFI
34
31
  yield mnt
35
32
  end
36
33
 
37
- fuse = FFI::Libfuse::Main.fuse_create(mnt, *args, operations: operations)
38
- raise FFI::Libfuse::Error, 'No fuse object returned from fuse_create' unless fuse
39
-
40
- # Rake owns INT
41
- fuse.default_traps.delete(:TERM)
42
- fuse.default_traps.delete(:INT)
43
-
44
- raise FFI::Libfuse::Error, 'fuse object is not mounted?' unless fuse.mounted?
34
+ run_fuse(mnt, *args, operations: operations, **options) do
35
+ # TODO: Work out why waitpid2 hangs on mac unless the process has already finished
36
+ sleep 10 if mac_fuse?
45
37
 
46
- t = Thread.new { fuse.run(foreground: true, **options) }
47
-
48
- # TODO: Work out why waitpid2 hangs on mac unless the process has already finished
49
- sleep 10 if mac_fuse?
50
-
51
- _pid, block_status = Process.waitpid2(fpid)
52
- block_exit = block_status.exitstatus
53
- fuse.exit('fuse_helper')&.join
54
- run_result = t.value
55
-
56
- raise FFI::Libfuse::Error, 'fuse is still mounted after fuse.exit' if fuse.mounted?
57
- raise FFI::Libfuse::Error, "forked file operations failed with #{block_exit}" unless block_exit.zero?
58
- raise FFI::Libfuse::Error, "fuse run failed #{run_result}" unless run_result.zero?
59
-
60
- if !mac_fuse? && mounted?(mnt)
61
- raise FFI::Libfuse::Error, "OS reports fuse is still mounted at #{mnt} after fuse.exit"
38
+ _pid, block_status = Process.waitpid2(fpid)
39
+ block_exit = block_status.exitstatus
40
+ raise FFI::Libfuse::Error, "forked file operations failed with #{block_exit}" unless block_exit.zero?
62
41
  end
63
-
64
- true
65
42
  end
66
43
  end
67
44
 
@@ -77,42 +54,28 @@ module FFI
77
54
  # @note if the filesystem is configured to daemonize then no output will be captured
78
55
  def run_filesystem(filesystem, *args, env: {})
79
56
  fsname = File.basename(filesystem)
80
- safe_fuse do |mnt|
81
- t = Thread.new do
82
- if defined?(Bundler)
83
- Bundler.with_unbundled_env do
84
- Open3.capture3(env, 'bundle', 'exec', filesystem.to_s, mnt, "-ofsname=#{fsname}", *args, binmode: true)
85
- end
86
- else
87
- Open3.capture3(env, filesystem.to_s, mnt, "-ofsname=#{fsname}", *args, binmode: true)
88
- end
89
- end
57
+
58
+ t, err = safe_fuse do |mnt|
59
+ t = Thread.new { open3_filesystem(args, env, filesystem, fsname, mnt) }
90
60
  sleep 1
91
61
 
92
62
  begin
93
- if block_given?
94
- raise Error, "#{fsname} not mounted at #{mnt}" unless mounted?(mnt, fsname)
95
-
96
- yield mnt
97
- end
98
- # rubocop:disable Lint/RescueException
99
- # Minitest::Assertion and other test assertion classes are not derived from StandardError
100
- rescue Exception => _err
101
- # rubocop:enable Lint/RescueException
102
- unmount(mnt) if mounted?(mnt)
103
- o, e, _s = t.value
104
- warn "Errors\n#{e}" unless e.empty?
105
- warn "Output\n#{o}" unless o.empty?
106
- raise
107
- end
63
+ raise Error, "#{fsname} not mounted at #{mnt}" if block_given? && !mounted?(mnt, fsname)
108
64
 
109
- unmount(mnt) if mounted?(mnt)
110
- o, e, s = t.value
111
- [o, e, s.exitstatus]
65
+ yield mnt if block_given?
66
+ [t, nil]
67
+ rescue Minitest::Assertion, StandardError => e
68
+ [t, e]
69
+ end
112
70
  end
71
+
72
+ o, e, s = t.value
73
+ return [o, e, s.exitstatus] unless err
74
+
75
+ warn "Errors\n#{e}" unless e.empty?
76
+ warn "Output\n#{o}" unless o.empty? Minitest::Assertion, StandardError => e
77
+ raise err
113
78
  end
114
- # rubocop:enable Metrics/AbcSize
115
- # rubocop:enable Metrics/MethodLength
116
79
 
117
80
  def mounted?(mnt, _filesystem = '.*')
118
81
  type, prefix = mac_fuse? ? %w[macfuse /private] : %w[fuse]
@@ -140,6 +103,51 @@ module FFI
140
103
  def mac_fuse?
141
104
  FFI::Platform::IS_MAC
142
105
  end
106
+
107
+ private
108
+
109
+ def open3_filesystem(args, env, filesystem, fsname, mnt)
110
+ if defined?(Bundler)
111
+ Bundler.with_unbundled_env do
112
+ Open3.capture3(env, 'bundle', 'exec', filesystem.to_s, mnt, "-ofsname=#{fsname}", *args, binmode: true)
113
+ end
114
+ else
115
+ Open3.capture3(env, filesystem.to_s, mnt, "-ofsname=#{fsname}", *args, binmode: true)
116
+ end
117
+ end
118
+
119
+ def run_fuse(mnt, *args, operations:, **options)
120
+ fuse = mount_fuse(mnt, *args, operations: operations)
121
+ begin
122
+ t = Thread.new do
123
+ Thread.current.name = 'Fuse Run'
124
+ # Rake owns INT
125
+ fuse.run(foreground: true, traps: { INT: nil, TERM: nil }, **options)
126
+ end
127
+
128
+ yield
129
+ ensure
130
+ fuse.exit('fuse_helper')&.join
131
+ run_result = t.value
132
+
133
+ raise FFI::Libfuse::Error, 'fuse is still mounted after fuse.exit' if fuse.mounted?
134
+ raise FFI::Libfuse::Error, "fuse run failed #{run_result}" unless run_result.zero?
135
+
136
+ if !mac_fuse? && mounted?(mnt)
137
+ raise FFI::Libfuse::Error, "OS reports fuse is still mounted at #{mnt} after fuse.exit"
138
+ end
139
+ end
140
+ end
141
+
142
+ def mount_fuse(mnt, *args, operations:)
143
+ operations.fuse_debug(args.include?('-d')) if operations.respond_to?(:fuse_debug)
144
+
145
+ fuse = FFI::Libfuse::Main.fuse_create(mnt, *args, operations: operations)
146
+ raise FFI::Libfuse::Error, 'No fuse object returned from fuse_create' unless fuse
147
+ raise FFI::Libfuse::Error, 'fuse object is not mounted?' unless fuse.mounted?
148
+
149
+ fuse
150
+ end
143
151
  end
144
152
  end
145
153
  end
@@ -3,6 +3,6 @@
3
3
  module FFI
4
4
  # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
5
5
  module Libfuse
6
- VERSION = '0.3.4'
6
+ VERSION = '0.4.0'
7
7
  end
8
8
  end
data/lib/ffi/libfuse.rb CHANGED
@@ -11,7 +11,7 @@ require_relative 'devt'
11
11
  module FFI
12
12
  # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
13
13
  module Libfuse
14
- # Filesystems can raise this error to indicate errors from filesystem users
14
+ # Filesystems can raise this error to indicate misconfiguration issues etc...
15
15
  class Error < StandardError; end
16
16
 
17
17
  # Opinionated default args for {.main}.
@@ -27,14 +27,14 @@ module FFI
27
27
  :unused, [:long, 3]
28
28
 
29
29
  [['', :string], ['l', :string], ['f', :int]].each do |(prefix, ftype)|
30
- native_func = "native_#{prefix}stat".to_sym
31
- lib_func = "#{prefix}stat".to_sym
30
+ native_func = :"native_#{prefix}stat"
31
+ lib_func = :"#{prefix}stat"
32
32
  begin
33
33
  ::FFI::Stat.attach_function native_func, lib_func, [ftype, by_ref], :int
34
34
  rescue FFI::NotFoundError
35
35
  # gLibc 2.31 (Ubuntu focal) does not export these functions, it maps them to __xstat variants
36
- native_xfunc = "native_#{prefix}xstat".to_sym
37
- lib_xfunc = "__#{prefix}xstat".to_sym
36
+ native_xfunc = :"native_#{prefix}xstat"
37
+ lib_xfunc = :"__#{prefix}xstat"
38
38
  ::FFI::Stat.attach_function native_xfunc, lib_xfunc, [:int, ftype, by_ref], :int
39
39
  # 1 is 64 bit versions of struct stat, 3 is 32 bit
40
40
  ::FFI::Stat.define_singleton_method(native_func) { |file, buf| send(native_xfunc, 1, file, buf) }
data/lib/ffi/stat.rb CHANGED
@@ -62,9 +62,9 @@ module FFI
62
62
  # @param [Integer] gid
63
63
  # @param [Hash] args additional system specific stat fields
64
64
  # @return [self]
65
- def file(mode:, size:, uid: Process.uid, gid: Process.gid, **args)
65
+ def file(mode:, size:, nlink: 1, uid: Process.uid, gid: Process.gid, **args)
66
66
  mode = ((S_IFREG & S_IFMT) | (mode & 0o777))
67
- fill(mode: mode, size: size, uid: uid, gid: gid, **args)
67
+ fill(mode: mode, size: size, nlink: nlink, uid: uid, gid: gid, **args)
68
68
  end
69
69
 
70
70
  # Fill content for a directory
@@ -80,6 +80,18 @@ module FFI
80
80
  end
81
81
  alias directory dir
82
82
 
83
+ # Fill content for a symbolic link
84
+ # @param [Integer] size length of the target name (including null terminator)
85
+ # @param [Integer] mode
86
+ # @param [Integer] uid
87
+ # @param [Integer] gid
88
+ # @param [Hash] args additional system specific stat fields
89
+ # @return [self]
90
+ def symlink(size:, mode: 0o777, nlink: 1, uid: Process.uid, gid: Process.gid, **args)
91
+ mode = ((S_IFLNK & S_IFMT) | (mode & 0o777))
92
+ fill(mode: mode, nlink: nlink, size: size, uid: uid, gid: gid, **args)
93
+ end
94
+
83
95
  # Fill attributes from file (using native LIBC calls)
84
96
  # @param [Integer|:to_s] file descriptor or a file path
85
97
  # @param [Boolean] follow links
@@ -123,7 +135,7 @@ module FFI
123
135
  # @param [Integer] mask (see umask)
124
136
  # @param [Hash] overrides see {fill}
125
137
  # @return self
126
- def mask(mask = 0o4000, **overrides)
138
+ def mask(mask = S_ISUID, **overrides)
127
139
  fill(mode: mode & (~mask), **overrides)
128
140
  end
129
141
 
@@ -147,6 +159,10 @@ module FFI
147
159
  mode & S_ISVTX != 0
148
160
  end
149
161
 
162
+ def symlink?
163
+ mode & S_IFLNK != 0
164
+ end
165
+
150
166
  class << self
151
167
  # @!method file(stat,**fields)
152
168
  # @return [Stat]
@@ -10,6 +10,7 @@ module FFI
10
10
  def array(size)
11
11
  ArrayConverter.new(self, size)
12
12
  end
13
+ alias [] array
13
14
 
14
15
  # @!visibility private
15
16
  # Helper to handle callbacks containing fixed length array of struct
@@ -28,7 +29,7 @@ module FFI
28
29
  def from_native(ptr, _ctx)
29
30
  return [] if ptr.null?
30
31
 
31
- Array.new(@size) { |i| @type.new(ptr + (i * @type.size)) }
32
+ Array.new(@size) { |i| @type.new(ptr + (i * @type.size)) }.freeze
32
33
  end
33
34
 
34
35
  def to_native(ary, _ctx)
data/sample/hello_fs.rb CHANGED
@@ -51,4 +51,4 @@ class HelloFS
51
51
  end
52
52
 
53
53
  # Start the file system
54
- FFI::Libfuse.fuse_main(operations: HelloFS.new) if __FILE__ == $0
54
+ exit(FFI::Libfuse.fuse_main(operations: HelloFS.new)) if __FILE__ == $0
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffi-libfuse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Grant Gardner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-08 00:00:00.000000000 Z
11
+ date: 2024-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -161,11 +161,13 @@ files:
161
161
  - lib/ffi/libfuse/filesystem/virtual_dir.rb
162
162
  - lib/ffi/libfuse/filesystem/virtual_file.rb
163
163
  - lib/ffi/libfuse/filesystem/virtual_fs.rb
164
+ - lib/ffi/libfuse/filesystem/virtual_link.rb
164
165
  - lib/ffi/libfuse/filesystem/virtual_node.rb
165
166
  - lib/ffi/libfuse/fuse2.rb
166
167
  - lib/ffi/libfuse/fuse3.rb
167
168
  - lib/ffi/libfuse/fuse_args.rb
168
- - lib/ffi/libfuse/fuse_buffer.rb
169
+ - lib/ffi/libfuse/fuse_buf.rb
170
+ - lib/ffi/libfuse/fuse_buf_vec.rb
169
171
  - lib/ffi/libfuse/fuse_callbacks.rb
170
172
  - lib/ffi/libfuse/fuse_cmdline_opts.rb
171
173
  - lib/ffi/libfuse/fuse_common.rb
@@ -180,6 +182,7 @@ files:
180
182
  - lib/ffi/libfuse/fuse_version.rb
181
183
  - lib/ffi/libfuse/gem_helper.rb
182
184
  - lib/ffi/libfuse/gem_version.rb
185
+ - lib/ffi/libfuse/io.rb
183
186
  - lib/ffi/libfuse/job_pool.rb
184
187
  - lib/ffi/libfuse/main.rb
185
188
  - lib/ffi/libfuse/test_helper.rb