ffi-libfuse 0.0.1.pre

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