ffi-libfuse 0.0.1.pre → 0.1.0.rc20220550

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -1
  3. data/CHANGES.md +14 -0
  4. data/LICENSE +21 -0
  5. data/README.md +127 -44
  6. data/lib/ffi/accessors.rb +6 -6
  7. data/lib/ffi/boolean_int.rb +27 -0
  8. data/lib/ffi/devt.rb +23 -0
  9. data/lib/ffi/encoding.rb +38 -0
  10. data/lib/ffi/gnu_extensions.rb +1 -1
  11. data/lib/ffi/libfuse/ackbar.rb +6 -8
  12. data/lib/ffi/libfuse/adapter/context.rb +12 -10
  13. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +52 -51
  14. data/lib/ffi/libfuse/adapter/fuse3_support.rb +0 -1
  15. data/lib/ffi/libfuse/adapter/ruby.rb +499 -148
  16. data/lib/ffi/libfuse/adapter/safe.rb +1 -1
  17. data/lib/ffi/libfuse/adapter.rb +1 -2
  18. data/lib/ffi/libfuse/callbacks.rb +1 -1
  19. data/lib/ffi/libfuse/filesystem/accounting.rb +116 -0
  20. data/lib/ffi/libfuse/filesystem/mapped_dir.rb +74 -0
  21. data/lib/ffi/libfuse/filesystem/mapped_files.rb +141 -0
  22. data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +55 -0
  23. data/lib/ffi/libfuse/filesystem/pass_through_file.rb +45 -0
  24. data/lib/ffi/libfuse/filesystem/utils.rb +102 -0
  25. data/lib/ffi/libfuse/filesystem/virtual_dir.rb +306 -0
  26. data/lib/ffi/libfuse/filesystem/virtual_file.rb +94 -0
  27. data/lib/ffi/libfuse/filesystem/virtual_fs.rb +188 -0
  28. data/lib/ffi/libfuse/filesystem/virtual_node.rb +101 -0
  29. data/lib/ffi/libfuse/filesystem.rb +25 -0
  30. data/lib/ffi/libfuse/fuse2.rb +21 -21
  31. data/lib/ffi/libfuse/fuse3.rb +12 -12
  32. data/lib/ffi/libfuse/fuse_args.rb +69 -34
  33. data/lib/ffi/libfuse/fuse_buffer.rb +128 -26
  34. data/lib/ffi/libfuse/fuse_callbacks.rb +1 -5
  35. data/lib/ffi/libfuse/fuse_common.rb +55 -61
  36. data/lib/ffi/libfuse/fuse_config.rb +134 -143
  37. data/lib/ffi/libfuse/fuse_conn_info.rb +310 -134
  38. data/lib/ffi/libfuse/fuse_context.rb +45 -3
  39. data/lib/ffi/libfuse/fuse_operations.rb +43 -19
  40. data/lib/ffi/libfuse/fuse_version.rb +10 -6
  41. data/lib/ffi/libfuse/main.rb +80 -37
  42. data/lib/ffi/libfuse/thread_pool.rb +1 -1
  43. data/lib/ffi/libfuse/version.rb +1 -1
  44. data/lib/ffi/libfuse.rb +13 -4
  45. data/lib/ffi/ruby_object.rb +1 -1
  46. data/lib/ffi/stat/constants.rb +9 -0
  47. data/lib/ffi/stat/native.rb +36 -6
  48. data/lib/ffi/stat/time_spec.rb +28 -12
  49. data/lib/ffi/stat.rb +111 -22
  50. data/lib/ffi/stat_vfs.rb +59 -1
  51. data/lib/ffi/struct_wrapper.rb +22 -1
  52. data/sample/hello_fs.rb +54 -0
  53. data/sample/memory_fs.rb +5 -181
  54. data/sample/no_fs.rb +20 -21
  55. data/sample/pass_through_fs.rb +30 -0
  56. metadata +66 -7
  57. data/lib/ffi/libfuse/adapter/thread_local_context.rb +0 -36
data/lib/ffi/stat.rb CHANGED
@@ -1,28 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'struct_wrapper'
4
- require_relative 'stat/native'
5
4
  require_relative 'stat/constants'
6
5
 
7
6
  module FFI
8
7
  # Ruby representation of stat.h struct
9
8
  class Stat
10
- class << self
11
- # @return [Stat] Newly allocated stat representing a regular file - see {Stat#file}
12
- def file(**fields)
13
- new.file(**fields)
14
- end
15
-
16
- # @return [Stat] Newly allocated stat representing a directory - see {Stat#dir}
17
- def dir(**fields)
18
- new.dir(**fields)
19
- end
20
- alias directory dir
21
- end
22
-
23
- # We need to be a StructWrapper because of clash with #size
9
+ # Use a StructWrapper because of clash with #size and the ability to attach functions
24
10
  include StructWrapper
25
11
 
12
+ extend FFI::Library
13
+ ffi_lib FFI::Library::LIBC
14
+
15
+ # stat/native will attach functions to Stat
16
+ require_relative 'stat/native'
26
17
  native_struct(Native)
27
18
 
28
19
  # @!attribute [rw] mode
@@ -56,16 +47,12 @@ module FFI
56
47
  # @!attribute [rw] ctime
57
48
  # @return [Time] time of last status change
58
49
 
59
- time_members = Native
60
- .members
61
- .select { |m| m.to_s.start_with?('st_') && m.to_s.end_with?('timespec') }
62
- .map { |m| m[3..-5].to_sym }
50
+ time_members = Native.members.select { |m| m.to_s =~ /^st_.*timespec$/ }.map { |m| m[3..-5].to_sym }
63
51
 
64
52
  ffi_attr_reader(*time_members, format: 'st_%sspec', &:time)
65
53
 
66
54
  ffi_attr_writer(*time_members, format: 'st_%sspec', simple: false) do |sec, nsec = 0|
67
- t = self[__method__[0..-2].to_sym]
68
- t.set_time(sec, nsec)
55
+ self[__method__[0..-2].to_sym].set_time(sec, nsec)
69
56
  end
70
57
 
71
58
  # Fill content for a regular file
@@ -87,10 +74,112 @@ module FFI
87
74
  # @param [Integer] gid
88
75
  # @param [Hash] args additional system specific stat fields
89
76
  # @return [self]
90
- def dir(mode:, nlink: 1, uid: Process.uid, gid: Process.gid, **args)
77
+ def dir(mode:, nlink: 3, uid: Process.uid, gid: Process.gid, **args)
91
78
  mode = ((S_IFDIR & S_IFMT) | (mode & 0o777))
92
79
  fill(mode: mode, uid: uid, gid: gid, nlink: nlink, **args)
93
80
  end
94
81
  alias directory dir
82
+
83
+ # Fill attributes from file (using native LIBC calls)
84
+ # @param [Integer|:to_s] file descriptor or a file path
85
+ # @param [Boolean] follow links
86
+ # @return [self]
87
+ def from(file, follow: true)
88
+ return fstat(file) if file.is_a?(Integer)
89
+
90
+ return stat(file.to_s) if follow
91
+
92
+ lstat(file.to_s)
93
+ end
94
+
95
+ # @!method stat(path)
96
+ # Fill attributes from file, following links
97
+ # @param [:to_s] path a file path
98
+ # @raise [SystemCallError] on error
99
+ # @return [self]
100
+
101
+ # @!method lstat(path)
102
+ # Fill attributes from file path, without following links
103
+ # @param [:to_s] path
104
+ # @raise [SystemCallError] on error
105
+ # @return [self]
106
+
107
+ # @!method fstat(fileno)
108
+ # Fill attributes from file descriptor
109
+ # @param [:to_i] fileno file descriptor
110
+ # @raise [SystemCallError] on error
111
+ # @return [self]
112
+
113
+ %i[stat lstat fstat].each do |m|
114
+ define_method(m) do |file|
115
+ res = self.class.send("native_#{m}", (m == :fstat ? file.to_i : file.to_s), native)
116
+ raise SystemCallError.new('', FFI::LastError.error) unless res.zero?
117
+
118
+ self
119
+ end
120
+ end
121
+
122
+ # Apply permissions mask to mode
123
+ # @param [Integer] mask (see umask)
124
+ # @param [Hash] overrides see {fill}
125
+ # @return self
126
+ def mask(mask = 0o4000, **overrides)
127
+ fill(mode: mode & (~mask), **overrides)
128
+ end
129
+
130
+ def file?
131
+ mode & S_IFREG != 0
132
+ end
133
+
134
+ def directory?
135
+ mode & S_IFDIR != 0
136
+ end
137
+
138
+ def setuid?
139
+ mode & S_ISUID != 0
140
+ end
141
+
142
+ def setgid?
143
+ mode & S_ISGID != 0
144
+ end
145
+
146
+ def sticky?
147
+ mode & S_ISVTX != 0
148
+ end
149
+
150
+ class << self
151
+ # @!method file(stat,**fields)
152
+ # @return [Stat]
153
+ # @raise [SystemCallError]
154
+ # @see Stat#file
155
+
156
+ # @!method dir(stat,**fields)
157
+ # @return [Stat]
158
+ # @raise [SystemCallError]
159
+ # @see Stat#dir
160
+ %i[file dir].each { |m| define_method(m) { |stat = new, **args| stat.send(m, **args) } }
161
+ alias directory dir
162
+
163
+ # @!method from(file, stat = new(), follow: false)
164
+ # @return [Stat]
165
+ # @raise [SystemCallError]
166
+ # @see Stat#from
167
+
168
+ # @!method stat(file, stat = new())
169
+ # @return [Stat]
170
+ # @raise [SystemCallError]
171
+ # @see Stat#stat
172
+
173
+ # @!method lstat(file, stat = new())
174
+ # @return [Stat]
175
+ # @raise [SystemCallError]
176
+ # @see Stat#lstat
177
+
178
+ # @!method fstat(file, stat = new())
179
+ # @return [Stat]
180
+ # @raise [SystemCallError]
181
+ # @see Stat#fstat
182
+ %i[from stat lstat fstat].each { |m| define_method(m) { |file, stat = new, **args| stat.send(m, file, **args) } }
183
+ end
95
184
  end
96
185
  end
data/lib/ffi/stat_vfs.rb CHANGED
@@ -75,7 +75,65 @@ module FFI
75
75
  # @!attribute [rw] namemax
76
76
  # @return [Integer] Maximum filename length
77
77
 
78
- int_members = members.select { |m| m =~ /^f_/ }.map { |m| m[2..].to_sym }
78
+ int_members = members.grep(/^f_/).map { |m| m[2..].to_sym }
79
79
  ffi_attr_accessor(*int_members, format: 'f_%s')
80
+
81
+ extend FFI::Library
82
+ ffi_lib FFI::Library::LIBC
83
+
84
+ attach_function :native_statvfs, :statvfs, [:string, by_ref], :int
85
+ attach_function :native_fstatvfs, :fstatvfs, [:int, by_ref], :int
86
+
87
+ # Fill from native statvfs for path
88
+ # @param [:to_s] path
89
+ # @return [self]
90
+ def statvfs(path)
91
+ res = self.class.native_statvfs(path.to_s, self)
92
+ raise SystemCallError.new('', FFI::LastError.errno) unless res.zero?
93
+
94
+ self
95
+ end
96
+
97
+ # Fill from native fstatvfs for fileno
98
+ # @param [Integer] fileno
99
+ # @return [self]
100
+ def fstatvfs(fileno)
101
+ res = self.class.native_fstatvfs(fileno, self)
102
+ raise SystemCallError.new('', FFI::LastError.errno) unless res.zero?
103
+
104
+ self
105
+ end
106
+
107
+ # File from native LIBC calls for file
108
+ # @param [Integer|:to_s] file a file descriptor or a file path
109
+ # @return [self]
110
+ def from(file)
111
+ return fstatvfs(file) if file.is_a?(Integer)
112
+
113
+ statvfs(file)
114
+ end
115
+
116
+ class << self
117
+ # @!method from(file)
118
+ # @return [StatVfs]
119
+ # @raise [SystemCallError]
120
+ # @see StatVfs#from
121
+
122
+ # @!method statvfs(file)
123
+ # @return [StatVfs]
124
+ # @raise [SystemCallError]
125
+ # @see StatVfs#statvfs
126
+
127
+ # @!method fstatvfs(file)
128
+ # @return [StatVfs]
129
+ # @raise [SystemCallError]
130
+ # @see StatVfs#fstatvfs
131
+ %i[from statvfs fstatvfs].each { |m| define_method(m) { |file, stat = new, **args| stat.send(m, file, **args) } }
132
+
133
+ # @!visibility private
134
+
135
+ # @!method native_statvfs(path, statvfs_buf)
136
+ # @!method native_fstatvfs(fd, statvfs_buf)
137
+ end
80
138
  end
81
139
  end
@@ -5,6 +5,16 @@ require_relative 'accessors'
5
5
 
6
6
  module FFI
7
7
  # Helper to wrap structs with ugly names and attribute clashes with FFI::Struct (eg size)
8
+ #
9
+ # @example
10
+ # class MyStruct
11
+ # include FFI::StructWrapper
12
+ # native_struct(MyNativeStruct)
13
+ #
14
+ # #!@attribute [rw] field
15
+ # ffi_attr_accessor :field
16
+ # end
17
+ #
8
18
  module StructWrapper
9
19
  # @!visibility private
10
20
  class ByReference < StructByReference
@@ -78,8 +88,9 @@ module FFI
78
88
 
79
89
  # @!parse extend ClassMethods
80
90
  # @!parse include Accessors
91
+ # @!parse extend Accessors::ClassMethods
81
92
 
82
- # @!visibility private
93
+ # @return [FFI::Struct] the underlying native struct
83
94
  attr_reader :native
84
95
 
85
96
  # @!visibility private
@@ -96,5 +107,15 @@ module FFI
96
107
  def []=(member_or_attr, val)
97
108
  @native[self.class.ffi_attr_writers.fetch(member_or_attr, member_or_attr)] = val
98
109
  end
110
+
111
+ # Pass unimplemented methods on to {#native} underlying struct
112
+ def method_missing(method, *args)
113
+ @native.send(method, *args)
114
+ end
115
+
116
+ # @!visibility private
117
+ def respond_to_missing?(method, private = false)
118
+ @native.respond_to?(method, private)
119
+ end
99
120
  end
100
121
  end
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'ffi/libfuse'
5
+
6
+ # Hello World!
7
+ class HelloFS
8
+ include FFI::Libfuse::Adapter::Ruby
9
+ include FFI::Libfuse::Adapter::Fuse2Compat
10
+
11
+ # FUSE Configuration methods
12
+
13
+ def fuse_options(args)
14
+ args.parse!({ 'subject=' => :subject }) do |key:, value:, **|
15
+ raise FFI::Libfuse::Error, 'subject option must be at least 2 characters' unless value.size >= 2
16
+
17
+ @subject = value if key == :subject
18
+ :handled
19
+ end
20
+ end
21
+
22
+ def fuse_help
23
+ '-o subject=<subject> a target to say hello to'
24
+ end
25
+
26
+ def fuse_configure
27
+ @subject ||= 'World!'
28
+ @content = "Hello #{@subject}\n"
29
+ end
30
+
31
+ # FUSE callbacks
32
+
33
+ def getattr(path, stat, *_args)
34
+ case path
35
+ when '/'
36
+ stat.directory(mode: 0o550)
37
+ when '/hello.txt'
38
+ stat.file(mode: 0o440, size: @content.size)
39
+ else
40
+ raise Errno::ENOENT
41
+ end
42
+ end
43
+
44
+ def readdir(_path, *_args)
45
+ yield 'hello.txt'
46
+ end
47
+
48
+ def read(_path, *_args)
49
+ @content
50
+ end
51
+ end
52
+
53
+ # Start the file system
54
+ FFI::Libfuse.fuse_main(operations: HelloFS.new) if __FILE__ == $0
data/sample/memory_fs.rb CHANGED
@@ -2,188 +2,12 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'ffi/libfuse'
5
+ require 'ffi/libfuse/filesystem/virtual_fs'
5
6
 
6
7
  # A simple in-memory filesystem defined with hashes.
7
- #
8
- # It is writable to the user that mounted it may create and edit files within it
9
- #
10
- # === Usage
11
- # root = Memory.new(files: { 'hello' => { 'world.txt' => 'Hello World'}})
12
- # root.mkdir("/hello")
13
- # root.("/hello/world","Hello World!\n")
14
- # root.write("/hello/everybody","Hello Everyone!\n")
15
- #
16
- # Libfuse::fuse_main($0,ARGV,operations: root)
17
- #
18
- #
19
- class MemoryFS
20
- # @return [Hash<String,Object>] list of file objects by path
21
- attr_reader :root
8
+ class MemoryFS < FFI::Libfuse::Filesystem::VirtualFS; end
22
9
 
23
- include FFI::Libfuse::Adapter::Fuse3Support
24
- include FFI::Libfuse::Adapter::Ruby
25
- include FFI::Libfuse::Adapter::Pathname
10
+ # Set this to test multi-threading etc...
11
+ main_class = ENV.fetch('MEMORY_FS_SKIP_DEFAULT_ARGS', 'N') == 'Y' ? FFI::Libfuse::Main : FFI::Libfuse
26
12
 
27
- File = Struct.new(:mode, :content, :ctime, :atime, :mtime) do
28
- def dig(*_args)
29
- raise Errno::ENOTDIR
30
- end
31
-
32
- def fill_stat(stat = FFI::Stat.new)
33
- stat.file(mode: mode, ctime: ctime, atime: atime, mtime: mtime, size: content.size)
34
- end
35
- end
36
-
37
- # rubocop:disable Lint/StructNewOverride
38
- Dir = Struct.new(:mode, :entries, :ctime, :atime, :mtime) do
39
- def dig(*args)
40
- entries.dig(*args)
41
- end
42
-
43
- def fill_stat(stat = FFI::Stat.new)
44
- stat.directory(mode: mode, ctime: ctime, atime: atime, mtime: mtime)
45
- end
46
- end
47
- # rubocop:enable Lint/StructNewOverride
48
-
49
- def initialize(files: {}, max_size: 100_000, max_files: 1_000)
50
- now = Time.now
51
- @root = Dir.new(0x755, {}, now, now, now)
52
- @total_size = 0
53
- @total_files = 1
54
- @max_size = max_size
55
- @max_files = max_files
56
-
57
- build(files)
58
- end
59
-
60
- def build(files, path = ::Pathname.new('/'))
61
- files.each_pair do |basename, content|
62
- raise 'Initial file keys must be String' unless basename.is_a?(String)
63
- raise 'Initial file keys must not contain path separators' if basename =~ %r{[/\\]}
64
-
65
- entry_path = path + basename
66
- case content
67
- when String
68
- create(entry_path, 0x644)
69
- write(entry_path, content, 0)
70
- when Hash
71
- mkdir(entry_path, 0x755)
72
- build(content, entry_path)
73
- else
74
- raise 'Initial files must be String or Hash'
75
- end
76
- end
77
- end
78
-
79
- def fuse_version
80
- 'MemoryFS: Version x.y.z'
81
- end
82
-
83
- def fuse_traps
84
- {
85
- HUP: -> { reload }
86
- }
87
- end
88
-
89
- def statfs(_path, statfs_buf)
90
- blocks = @total_size / 1_000
91
- statfs_buf.bsize = 1 # block size (in Kb)
92
- statfs_buf.frsize = 1 # fragment size pretty much always bsize
93
- statfs_buf.blocks = @max_size
94
- statfs_buf.bfree = @max_size - blocks
95
- statfs_buf.bavail = @max_size - blocks
96
- statfs_buf.files = @max_files
97
- statfs_buf.ffree = @max_files - @total_files
98
- statfs_buf.favail = @max_files - @total_files
99
- 0
100
- end
101
-
102
- def getattr(path, stat_buf)
103
- entry = find(path)
104
- return -Errno::ENOENT::Errno unless entry
105
-
106
- entry.fill_stat(stat_buf)
107
- 0
108
- end
109
-
110
- def readdir(path, _offset, _ffi)
111
- %w[. ..].each { |d| yield(d, nil) }
112
- dir = find(path)
113
- dir.entries.each_pair { |k, e| yield(k, e.fill_stat) }
114
- end
115
-
116
- def create(path, mode, _ffi)
117
- dir_entries = find(path.dirname).entries
118
- now = Time.now
119
- dir_entries[path.basename.to_s] = File.new(mode, String.new, now, now, now)
120
- @total_files += 1
121
- 0
122
- end
123
-
124
- # op[:read] = [:pointer, :size_t, :off_t, FuseFileInfo.by_ref]
125
- def read(path, len, off, _ffi)
126
- file = find(path)
127
- file.atime = Time.now.utc
128
- FFI::Libfuse::ThreadPool.busy
129
- sleep 0.5
130
- file.content[off, len]
131
- end
132
-
133
- # write(const char* path, char *buf, size_t size, off_t offset, struct fuse_file_info* fi)
134
- def write(path, data, offset, _ffi)
135
- file = find(path)
136
- content = file.content
137
- @total_size -= content.size
138
- content[offset, data.length] = data
139
- @total_size += content.size
140
- file.mtime = Time.now.utc
141
- end
142
-
143
- def truncate(path, size)
144
- file = find(path)
145
- @total_size -= file.content.size
146
- file.content[size..-1] = ''
147
- file.mtime = Time.now.utc
148
- @total_size += file.content.size
149
- 0
150
- end
151
-
152
- def unlink(path)
153
- dir = find(path.dirname)
154
- deleted = dir.entries.delete(path.basename.to_s)
155
- @total_files -= 1
156
- @total_size -= deleted.content.size if deleted.is_a?(File)
157
- 0
158
- end
159
-
160
- def mkdir(path, mode)
161
- entries = find(path.dirname).entries
162
- now = Time.now
163
- entries[path.basename.to_s] = Dir.new(mode, {}, now, now, now)
164
- end
165
-
166
- def rmdir(path)
167
- dir = find(path)
168
- raise Errno::ENOTDIR unless dir.is_a?(Dir)
169
- raise Errno::ENOTEMPTY unless dir.entries.empty?
170
-
171
- find(path.dirname).entries.delete(path.basename.to_s)
172
- 0
173
- end
174
-
175
- def utimens(path, atime, mtime)
176
- entry = find(path)
177
- entry.atime = atime if atime
178
- entry.mtime = mtime if mtime
179
- 0
180
- end
181
-
182
- private
183
-
184
- def find(path)
185
- path.root? ? root : root.dig(*path.to_s.split('/')[1..])
186
- end
187
- end
188
-
189
- exit(FFI::Libfuse.fuse_main($0, *ARGV, operations: MemoryFS.new)) if __FILE__ == $0
13
+ exit(main_class.fuse_main(operations: MemoryFS.new)) if __FILE__ == $0
data/sample/no_fs.rb CHANGED
@@ -6,13 +6,21 @@ require 'ffi/libfuse'
6
6
  # An empty file system
7
7
  class NoFS
8
8
  include FFI::Libfuse::Adapter::Context
9
- include FFI::Libfuse::Adapter::Fuse3Support
10
9
  include FFI::Libfuse::Adapter::Ruby
10
+ include FFI::Libfuse::Adapter::Fuse3Support # must run outside of Adapter::Ruby
11
11
 
12
12
  OPTIONS = { 'log=' => :log }.freeze
13
13
 
14
- def fuse_options
15
- OPTIONS
14
+ def fuse_options(args)
15
+ args.parse!(OPTIONS) do |key:, value:, **|
16
+ case key
17
+ when :log
18
+ @logfile = value
19
+ else
20
+ next :keep
21
+ end
22
+ :handled
23
+ end
16
24
  end
17
25
 
18
26
  def fuse_help
@@ -24,46 +32,37 @@ class NoFS
24
32
  end
25
33
 
26
34
  def fuse_version
27
- 'NoFS: Version x.y.z'
28
- end
29
-
30
- def fuse_opt_proc(_data, arg, key, _outargs)
31
- case key
32
- when :log
33
- @logfile = arg[4..]
34
- return :handled
35
- end
36
- :keep
35
+ "NoFS: Version x.y.z. Fuse3Compat=#{fuse3_compat?}"
37
36
  end
38
37
 
39
- def getattr(_ctx, path, stat)
38
+ def getattr(path, stat)
40
39
  raise Errno::ENOENT unless path == '/'
41
40
 
42
41
  stat.directory(mode: 0o555)
43
42
  end
44
43
 
45
- def readdir(_ctx, _path, _offset, _ffi)
46
- %w[. ..].each { |d| yield(d, nil) }
44
+ def readdir(_path, _offset, _ffi, &block)
45
+ puts "NOFS Readdir: #{block}"
46
+ %w[. ..].each(&block)
47
47
  end
48
48
 
49
49
  def log
50
50
  @log ||= File.open(@logfile || '/tmp/no_fs.out', 'a')
51
51
  end
52
52
 
53
- def init(ctx, conn, cfg = nil)
53
+ def init(_conn)
54
+ ctx = FFI::Libfuse::Adapter::Context.fuse_context
54
55
  log.puts("NoFS init ctx- #{ctx.inspect}") if ctx
55
- log.puts("NoFS init conn - #{conn.inspect}") if conn && !conn.null?
56
- log.puts "NoFS init cfg #{cfg.inspect}" if cfg && !cfg.null?
57
56
  warn 'NoFS: DEBUG enabled' if debug?
58
57
  log.flush
59
58
  'INIT_DATA'
60
59
  end
61
60
 
62
- def destroy(obj, *_rest)
61
+ def destroy(obj)
63
62
  # If the fs is not cleanly unmounted the init data will have been GC'd by the time this is called
64
63
  log.puts("NoFS destroy- #{obj.inspect}") if !obj.is_a?(WeakRef) || obj.weakref_alive?
65
64
  log.puts "NoFS destroy- pid=#{Process.pid}"
66
65
  end
67
66
  end
68
67
 
69
- exit(FFI::Libfuse.fuse_main($0, *ARGV, operations: NoFS.new, private_data: 'MAIN_DATA')) if __FILE__ == $0
68
+ exit(FFI::Libfuse::Main.fuse_main($0, *ARGV, operations: NoFS.new, private_data: 'MAIN_DATA')) if __FILE__ == $0
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'ffi/libfuse'
5
+ require 'ffi/libfuse/filesystem/pass_through_dir'
6
+
7
+ # Pass Through Filesystem - over a base directory
8
+ class PassThroughFS < FFI::Libfuse::Filesystem::PassThroughDir
9
+ def fuse_options(args)
10
+ args.parse!({ 'base_dir=' => :base_dir }) do |key:, value:, **|
11
+ next :keep unless key == :base_dir
12
+
13
+ raise FFI::Libfuse::Error, "#{value} is not a directory" unless Dir.exist?(value)
14
+
15
+ self.base_dir = value
16
+ :handled
17
+ end
18
+ end
19
+
20
+ def fuse_help
21
+ '-o base_dir=<dir>'
22
+ end
23
+
24
+ def fuse_configure
25
+ self.base_dir ||= Dir.pwd
26
+ warn "Using #{self.base_dir} as base directory for file operations" if debug?
27
+ end
28
+ end
29
+
30
+ exit(FFI::Libfuse.fuse_main(operations: PassThroughFS.new)) if __FILE__ == $0