ffi-libfuse 0.0.1.pre → 0.1.0.rc20220550

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