ffi-libfuse 0.0.1.pre

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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -0
  3. data/README.md +100 -0
  4. data/lib/ffi/accessors.rb +145 -0
  5. data/lib/ffi/devt.rb +30 -0
  6. data/lib/ffi/flock.rb +47 -0
  7. data/lib/ffi/gnu_extensions.rb +115 -0
  8. data/lib/ffi/libfuse/ackbar.rb +112 -0
  9. data/lib/ffi/libfuse/adapter/context.rb +37 -0
  10. data/lib/ffi/libfuse/adapter/debug.rb +89 -0
  11. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +91 -0
  12. data/lib/ffi/libfuse/adapter/fuse3_support.rb +87 -0
  13. data/lib/ffi/libfuse/adapter/interrupt.rb +37 -0
  14. data/lib/ffi/libfuse/adapter/pathname.rb +23 -0
  15. data/lib/ffi/libfuse/adapter/ruby.rb +334 -0
  16. data/lib/ffi/libfuse/adapter/safe.rb +58 -0
  17. data/lib/ffi/libfuse/adapter/thread_local_context.rb +36 -0
  18. data/lib/ffi/libfuse/adapter.rb +79 -0
  19. data/lib/ffi/libfuse/callbacks.rb +61 -0
  20. data/lib/ffi/libfuse/fuse2.rb +159 -0
  21. data/lib/ffi/libfuse/fuse3.rb +162 -0
  22. data/lib/ffi/libfuse/fuse_args.rb +166 -0
  23. data/lib/ffi/libfuse/fuse_buffer.rb +155 -0
  24. data/lib/ffi/libfuse/fuse_callbacks.rb +48 -0
  25. data/lib/ffi/libfuse/fuse_cmdline_opts.rb +44 -0
  26. data/lib/ffi/libfuse/fuse_common.rb +249 -0
  27. data/lib/ffi/libfuse/fuse_config.rb +205 -0
  28. data/lib/ffi/libfuse/fuse_conn_info.rb +211 -0
  29. data/lib/ffi/libfuse/fuse_context.rb +79 -0
  30. data/lib/ffi/libfuse/fuse_file_info.rb +100 -0
  31. data/lib/ffi/libfuse/fuse_loop_config.rb +43 -0
  32. data/lib/ffi/libfuse/fuse_operations.rb +870 -0
  33. data/lib/ffi/libfuse/fuse_opt.rb +54 -0
  34. data/lib/ffi/libfuse/fuse_poll_handle.rb +59 -0
  35. data/lib/ffi/libfuse/fuse_version.rb +43 -0
  36. data/lib/ffi/libfuse/job_pool.rb +53 -0
  37. data/lib/ffi/libfuse/main.rb +200 -0
  38. data/lib/ffi/libfuse/test/operations.rb +56 -0
  39. data/lib/ffi/libfuse/test.rb +3 -0
  40. data/lib/ffi/libfuse/thread_pool.rb +147 -0
  41. data/lib/ffi/libfuse/version.rb +8 -0
  42. data/lib/ffi/libfuse.rb +24 -0
  43. data/lib/ffi/ruby_object.rb +95 -0
  44. data/lib/ffi/stat/constants.rb +29 -0
  45. data/lib/ffi/stat/native.rb +50 -0
  46. data/lib/ffi/stat/time_spec.rb +137 -0
  47. data/lib/ffi/stat.rb +96 -0
  48. data/lib/ffi/stat_vfs.rb +81 -0
  49. data/lib/ffi/struct_array.rb +39 -0
  50. data/lib/ffi/struct_wrapper.rb +100 -0
  51. data/sample/memory_fs.rb +189 -0
  52. data/sample/no_fs.rb +69 -0
  53. metadata +165 -0
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'test/operations'
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module FFI
6
+ module Libfuse
7
+ # A self-expanding and self-limiting ThreadPool
8
+ #
9
+ # The first thread is created on ThreadPool.new and additional threads are added through ThreadPool.busy
10
+ # called from within a worker iteration
11
+ #
12
+ # A pool thread will end when
13
+ #
14
+ # * a worker iteration returns false or nil
15
+ # * a worker thread raises an exception (silently for StopIteration)
16
+ # * max_idle_threads is exceeded
17
+ class ThreadPool
18
+ class << self
19
+ # Starts a new thread if the current thread is a thread pool member and there are no other idle threads in the
20
+ # pool. The thread is marked as busy for the duration of the yield.
21
+ #
22
+ # If the current thread is not a pool member then simply yields
23
+ def busy(&block)
24
+ if (tp = Thread.current[:tp])
25
+ tp.busy(&block)
26
+ elsif block_given?
27
+ yield
28
+ end
29
+ end
30
+ end
31
+
32
+ # @return [ThreadGroup] the enclosed thread group to which the pool's threads are added
33
+ attr_reader :group
34
+
35
+ # Create a new ThreadPool
36
+ #
37
+ # @param [Integer] max_idle The maximum number of idle threads (>= 0)
38
+ # @param [Integer] max_active The maximum number of active threads (> 0)
39
+ # @param [String] name A prefix used to set Thread.name for pool threads
40
+ # @param [Proc] worker The worker called repeatedly within each pool thread
41
+ # @see ThreadPool.busy
42
+ def initialize(max_idle: nil, max_active: nil, name: nil, &worker)
43
+ raise ArgumentError, "max_active #{max_active} must be > 0" if max_active && !max_active.positive?
44
+ raise ArgumentError, "max_idle: #{max_idle} must be >= 0" if max_idle&.negative?
45
+ raise ArgumentError, 'must have worker block but none given' unless worker
46
+
47
+ @max_idle = max_idle
48
+ @max_active = max_active
49
+ @name = name
50
+ @worker = worker
51
+ @mutex = Mutex.new
52
+ @size = 0
53
+ @busy = 0
54
+ @idle_death = Set.new
55
+ @completed = Queue.new
56
+ @group = ThreadGroup.new.add(synchronize { start_thread }).enclose
57
+ end
58
+
59
+ # Join the ThreadPool optionally handling thread completion
60
+ # @return [void]
61
+ # @yield (thread, error = nil)
62
+ # @yieldparam [Thread] thread a Thread that has finished
63
+ # @yieldparam [StandardError] error if thread raised an exception
64
+ def join
65
+ while (t = @completed.pop)
66
+ begin
67
+ t.join
68
+ yield t if block_given?
69
+ rescue StandardError => e
70
+ yield t, e if block_given?
71
+ end
72
+ end
73
+ self
74
+ end
75
+
76
+ # @!visibility private
77
+ def busy
78
+ mark_busy
79
+ yield if block_given?
80
+ ensure
81
+ ensure_not_busy if block_given?
82
+ end
83
+
84
+ # @return [Array<Thread>,Array<Thread>] busy,idle threads
85
+ def list
86
+ group.list.partition { |t| t[:tp_busy] }
87
+ end
88
+
89
+ private
90
+
91
+ def mark_busy
92
+ return if Thread.current[:tp_busy]
93
+
94
+ Thread.current[:tp_busy] = true
95
+ synchronize { start_thread if (@busy += 1) == @size }
96
+ end
97
+
98
+ attr_reader :mutex, :worker, :name
99
+
100
+ def start_thread
101
+ return if @max_active && @size >= @max_active
102
+
103
+ @size += 1
104
+ Thread.new { worker_thread }
105
+ end
106
+
107
+ def worker_thread
108
+ Thread.current.name = "#{name}-#{Thread.current.object_id.to_s(16)}" if name
109
+ Thread.current[:tp] = self
110
+ loop while invoke && !idle_limit_exceeded?
111
+ ensure
112
+ @completed << Thread.current
113
+ @completed.close if decrement_size.zero?
114
+ end
115
+
116
+ def invoke
117
+ worker.call
118
+ ensure
119
+ ensure_not_busy
120
+ end
121
+
122
+ def ensure_not_busy
123
+ return unless Thread.current[:tp_busy]
124
+
125
+ Thread.current[:tp_busy] = false
126
+ synchronize { @busy -= 1 }
127
+ end
128
+
129
+ def decrement_size
130
+ synchronize do
131
+ @idle_death.delete(Thread.current)
132
+ @size -= 1
133
+ end
134
+ end
135
+
136
+ def idle_limit_exceeded?
137
+ return false unless @max_idle
138
+
139
+ synchronize { (@size - @busy - @idle_death.size - 1) > @max_idle && @idle_death << Thread.current }
140
+ end
141
+
142
+ def synchronize(&block)
143
+ @mutex.synchronize(&block)
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFI
4
+ # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
5
+ module Libfuse
6
+ VERSION = '0.0.1'
7
+ end
8
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'libfuse/fuse_version'
4
+ require_relative 'libfuse/fuse2' if FFI::Libfuse::FUSE_MAJOR_VERSION == 2
5
+ require_relative 'libfuse/fuse3' if FFI::Libfuse::FUSE_MAJOR_VERSION == 3
6
+ require_relative 'libfuse/main'
7
+ require_relative 'libfuse/adapter'
8
+ require_relative 'devt'
9
+
10
+ module FFI
11
+ # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
12
+ module Libfuse
13
+ class << self
14
+ # Filesystem entry point
15
+ # @note This main function defaults to single-threaded operation by injecting the '-s' option. Pass `$0,*ARGV`
16
+ # if your filesystem can usefully support multi-threaded operation.
17
+ #
18
+ # @see Main.fuse_main
19
+ def fuse_main(*argv, operations:, args: argv.any? ? argv : [$0, '-s', *ARGV], private_data: nil)
20
+ Main.fuse_main(args: args, operations: operations, private_data: private_data) || -1
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'weakref'
4
+ require 'ffi'
5
+
6
+ module FFI
7
+ # FFI::DataConverter to pass ruby objects as void*
8
+ #
9
+ # Objects are held in a global map of object id to WeakRef of the object.
10
+ #
11
+ # ie caller that puts a RubyObject into a library is responsible for keeping that object from the GC.
12
+ #
13
+ # Note this relies on the GVL to be threadsafe(ish)
14
+ module RubyObject
15
+ # Convert to cast ruby object *long to arbitrary integer type
16
+ # @!visibility private
17
+ class CastAsInt
18
+ include DataConverter
19
+ attr_reader :native_type
20
+
21
+ def initialize(int_type)
22
+ @native_type = FFI.find_type(int_type)
23
+ end
24
+
25
+ def to_native(obj, context)
26
+ return 0 if obj.nil?
27
+
28
+ RubyObject.to_native(obj, context).get(:long, 0)
29
+ end
30
+
31
+ def from_native(object_id, _context)
32
+ return nil if object_id.zero?
33
+
34
+ _ptr, obj = RubyObject.cache[object_id]
35
+ obj
36
+ end
37
+ end
38
+
39
+ extend FFI::DataConverter
40
+ native_type FFI::Type::POINTER
41
+
42
+ # rubocop:disable Lint/HashCompareByIdentity
43
+ class << self
44
+ # Store a ruby object reference in an integer type
45
+ # @param [FFI::Type] int_type
46
+ def by_object_id(int_type = :long)
47
+ CastAsInt.new(int_type)
48
+ end
49
+
50
+ # @!visibility private
51
+
52
+ # convert pointer to ruby object
53
+ def to_native(obj, _context)
54
+ return FFI::Pointer::NULL if obj.nil?
55
+
56
+ # return previous pointer if we've stored this object already
57
+ # not threadsafe!
58
+ ptr, _ref = cache[obj.object_id] ||= store(obj)
59
+ # Keep the pointer address as a type safety check, so we don't read memory we didn't store
60
+ cache[ptr.address.object_id] = true
61
+ ptr
62
+ end
63
+
64
+ def from_native(ptr, _context)
65
+ return nil if ptr.null?
66
+ raise TypeError, "No RubyObject stored at #{ptr.address}" unless cache.key?(ptr.address.object_id)
67
+
68
+ _ptr, obj = cache[ptr.get(:long, 0)]
69
+ obj
70
+ end
71
+
72
+ def cache
73
+ @cache ||= {}
74
+ end
75
+
76
+ def finalizer(*keys)
77
+ proc { keys.each { cache.delete(key) } }
78
+ end
79
+
80
+ def store(obj)
81
+ ptr = FFI::MemoryPointer.new(:long)
82
+ ptr.put(:long, 0, obj.object_id)
83
+
84
+ unless obj.frozen?
85
+ # Clean the cache when obj is finalised
86
+ ObjectSpace.define_finalizer(obj, finalizer(obj.object_id, ptr.address.object_id))
87
+ obj = WeakRef.new(obj)
88
+ end
89
+
90
+ [ptr, obj]
91
+ end
92
+ end
93
+ # rubocop:enable Lint/HashCompareByIdentity
94
+ end
95
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFI
4
+ class Stat
5
+ # File type mask
6
+ S_IFMT = 0o170000
7
+
8
+ # FIFO
9
+ S_IFIFO = 0o010000
10
+
11
+ # Character device
12
+ S_IFCHR = 0o020000
13
+
14
+ # Directory
15
+ S_IFDIR = 0o040000
16
+
17
+ # Block device
18
+ S_IFBLK = 0o060000
19
+
20
+ # Regular file
21
+ S_IFREG = 0o100000
22
+
23
+ # Symbolic link
24
+ S_IFLNK = 0o120000
25
+
26
+ # Socket
27
+ S_IFSOCK = 0o140000
28
+ end
29
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+ require_relative 'time_spec'
5
+
6
+ module FFI
7
+ class Stat
8
+ # Native (and naked) stat from stat.h
9
+ # @!visibility private
10
+ class Native < Struct
11
+ case Platform::NAME
12
+
13
+ when 'x86_64-linux'
14
+ layout :st_dev, :dev_t,
15
+ :st_ino, :ino_t,
16
+ :st_nlink, :nlink_t,
17
+ :st_mode, :mode_t,
18
+ :st_uid, :uid_t,
19
+ :st_gid, :gid_t,
20
+ :__pad0, :int,
21
+ :st_rdev, :dev_t,
22
+ :st_size, :off_t,
23
+ :st_blksize, :blksize_t,
24
+ :st_blocks, :blkcnt_t,
25
+ :st_atimespec, TimeSpec,
26
+ :st_mtimespec, TimeSpec,
27
+ :st_ctimespec, TimeSpec
28
+
29
+ when 'x65_64-darwin'
30
+ layout :st_dev, :dev_t,
31
+ :st_ino, :uint32,
32
+ :st_mode, :mode_t,
33
+ :st_nlink, :nlink_t,
34
+ :st_uid, :uid_t,
35
+ :st_gid, :gid_t,
36
+ :st_rdev, :dev_t,
37
+ :st_atimespec, TimeSpec,
38
+ :st_mtimespec, TimeSpec,
39
+ :st_ctimespec, TimeSpec,
40
+ :st_size, :off_t,
41
+ :st_blocks, :blkcnt_t,
42
+ :st_blksize, :blksize_t,
43
+ :st_flags, :uint32,
44
+ :st_gen, :uint32
45
+ else
46
+ raise NotImplementedError, "FFI::Stat not implemented for FFI::Platform #{Platform::NAME}"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+ require 'ffi/struct_array'
5
+
6
+ module FFI
7
+ class Stat
8
+ # Timespec from stat.h
9
+ class TimeSpec < FFI::Struct
10
+ extend StructArray
11
+
12
+ # Special nsec value representing the current time - see utimensat(2)
13
+ UTIME_NOW = (1 << 30) - 1
14
+ # Special nsec value representing a request to omit setting this time - see utimensat(2)
15
+ UTIME_OMIT = (1 << 30) - 2
16
+
17
+ # A fixed TimeSpec representing the current time
18
+ def self.now
19
+ @now ||= new.set_time(0, UTIME_NOW)
20
+ end
21
+
22
+ # A fixed TimeSpec representing a request to omit setting this time
23
+ def self.omit
24
+ @omit ||= new.set_time(0, UTIME_OMIT)
25
+ end
26
+
27
+ layout(
28
+ tv_sec: :time_t,
29
+ tv_nsec: :long
30
+ )
31
+
32
+ # @!attribute [r] tv_sec
33
+ # @return [Integer] number of seconds since epoch
34
+ def tv_sec
35
+ self[:tv_sec]
36
+ end
37
+ alias sec tv_sec
38
+
39
+ # @!attribute [r] tv_nsec
40
+ # @return [Integer] additional number of nanoseconds
41
+ def tv_nsec
42
+ self[:tv_nsec]
43
+ end
44
+ alias nsec tv_nsec
45
+
46
+ # @overload set_time(time)
47
+ # @param [Time] time
48
+ # @return [TimeSpec] self
49
+ # @overload set_time(sec,nsec=0)
50
+ # @param [Integer] sec number of (nano/micro)seconds from epoch, precision depending on nsec
51
+ # @param [Symbol|Integer] nsec
52
+ # - :nsec to treat sec as number of nanoseconds since epoch
53
+ # - :usec to treat sec as number of microseconds since epoch
54
+ # - Integer to treat sec as number of seconds since epoch, and nsec as additional nanoseconds
55
+ #
56
+ # @return [TimeSpec] self
57
+ def set_time(sec, nsec = 0)
58
+ return set_time(sec.to_i, sec.nsec) if sec.is_a?(Time)
59
+
60
+ case nsec
61
+ when :nsec
62
+ return set_time(sec / (10**9), sec % (10**9))
63
+ when :usec
64
+ return set_time(sec / (10**6), sec % (10**6))
65
+ when Integer
66
+ self[:tv_sec] = sec
67
+ self[:tv_nsec] = nsec
68
+ else
69
+ raise ArgumentError, "Invalid nsec=#{nsec}"
70
+ end
71
+
72
+ self
73
+ end
74
+ alias :time= set_time
75
+
76
+ # @return [Boolean] true if this value represents the special value {UTIME_NOW}
77
+ def now?
78
+ tv_nsec == UTIME_NOW
79
+ end
80
+
81
+ # @return [Boolean] true if this value represents the special value {UTIME_OMIT}
82
+ def omit?
83
+ tv_nsec == UTIME_OMIT
84
+ end
85
+
86
+ # Convert to Time
87
+ # @param [Time|nil] now
88
+ # optional value to use if {now?} is true. If not set then Time.now will be used
89
+ # @return [nil] if {omit?} is true
90
+ # @return [Time] this value as ruby Time in UTC
91
+ def time(now = nil)
92
+ return nil if omit?
93
+ return (now || Time.now).utc if now?
94
+
95
+ Time.at(sec, nsec, :nsec, in: 0).utc
96
+ end
97
+
98
+ # Convert to Integer
99
+ # @param [Time|nil] now
100
+ # optional value to use if {now?} is true. If not set then Time.now will be used
101
+ # @return [nil] if {omit?} is true
102
+ # @return [Integer] number of nanoseconds since epoch
103
+ def nanos(now = nil)
104
+ return nil if omit?
105
+
106
+ t = now? ? (now || Time.now) : self
107
+ t.tv_sec * 10**9 + t.tv_nsec
108
+ end
109
+
110
+ # Convert to Float
111
+ # @param [Time|nil] now
112
+ # optional value to use if {now?} is true. If not set then Time.now will be used
113
+ # @return [nil] if {omit?} is true
114
+ # @return [Float] seconds and fractional seconds since epoch
115
+ def to_f(now = nil)
116
+ return nil if omit?
117
+
118
+ t = now? ? (now || Time.now) : self
119
+ t.tv_sec.to_f + t.tv_nsec.to_f / (10**9)
120
+ end
121
+
122
+ # @!visibility private
123
+ def inspect
124
+ ns =
125
+ case nsec
126
+ when UTIME_NOW
127
+ 'NOW'
128
+ when UTIME_OMIT
129
+ 'OMIT'
130
+ else
131
+ nsec
132
+ end
133
+ "#{self.class.name}:#{sec}.#{ns}"
134
+ end
135
+ end
136
+ end
137
+ end