fanotify 1.0.0

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.
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fanotify
4
+ # Immutable description of a fanotify mark operation.
5
+ Mark = Data.define(:action, :path, :events, :on, :follow, :only_dir)
6
+ end
@@ -0,0 +1,458 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fanotify
4
+ CLASS_FLAGS = {
5
+ notif: FAN_CLASS_NOTIF,
6
+ content: FAN_CLASS_CONTENT,
7
+ pre_content: FAN_CLASS_PRE_CONTENT
8
+ }.freeze
9
+
10
+ REPORT_FLAGS = {
11
+ tid: FAN_REPORT_TID,
12
+ fid: FAN_REPORT_FID,
13
+ dir_fid: FAN_REPORT_DIR_FID,
14
+ name: FAN_REPORT_NAME,
15
+ dfid_name: FAN_REPORT_DFID_NAME,
16
+ target_fid: FAN_REPORT_TARGET_FID | FAN_REPORT_FID,
17
+ pidfd: FAN_REPORT_PIDFD
18
+ }.freeze
19
+
20
+ EVENT_FLAGS = {
21
+ rdonly: O_RDONLY,
22
+ wronly: O_WRONLY,
23
+ rdwr: O_RDWR,
24
+ largefile: O_LARGEFILE,
25
+ cloexec: O_CLOEXEC,
26
+ nonblock: O_NONBLOCK
27
+ }.freeze
28
+
29
+ EVENT_MASKS = {
30
+ access: FAN_ACCESS,
31
+ modify: FAN_MODIFY,
32
+ attrib: FAN_ATTRIB,
33
+ close_write: FAN_CLOSE_WRITE,
34
+ close_nowrite: FAN_CLOSE_NOWRITE,
35
+ open: FAN_OPEN,
36
+ moved_from: FAN_MOVED_FROM,
37
+ moved_to: FAN_MOVED_TO,
38
+ create: FAN_CREATE,
39
+ delete: FAN_DELETE,
40
+ delete_self: FAN_DELETE_SELF,
41
+ move_self: FAN_MOVE_SELF,
42
+ open_exec: FAN_OPEN_EXEC,
43
+ overflow: FAN_Q_OVERFLOW,
44
+ fs_error: FAN_FS_ERROR,
45
+ open_perm: FAN_OPEN_PERM,
46
+ access_perm: FAN_ACCESS_PERM,
47
+ open_exec_perm: FAN_OPEN_EXEC_PERM,
48
+ pre_access: FAN_PRE_ACCESS,
49
+ event_on_child: FAN_EVENT_ON_CHILD,
50
+ rename: FAN_RENAME,
51
+ ondir: FAN_ONDIR
52
+ }.freeze
53
+
54
+ # Owns a fanotify group and provides safe event iteration.
55
+ class Notifier
56
+ PERMISSION_EVENTS = %i[open_perm access_perm open_exec_perm pre_access].freeze
57
+ PERMISSION_MASK = PERMISSION_EVENTS.reduce(0) { |mask, event| mask | EVENT_MASKS.fetch(event) }
58
+ FID_EVENTS = %i[attrib create delete delete_self moved_from moved_to move_self rename].freeze
59
+ MARK_ACTIONS = {add: FAN_MARK_ADD, remove: FAN_MARK_REMOVE}.freeze
60
+ MARK_TARGETS = {inode: FAN_MARK_INODE, mount: FAN_MARK_MOUNT, filesystem: FAN_MARK_FILESYSTEM}.freeze
61
+
62
+ attr_reader :report
63
+
64
+ def self.open(**options)
65
+ notifier = new(**options)
66
+ return notifier unless block_given?
67
+
68
+ begin
69
+ yield notifier
70
+ ensure
71
+ notifier.close
72
+ end
73
+ end
74
+
75
+ def initialize(**options)
76
+ klass = options.delete(:class) || :notif
77
+ @report = Array(options.delete(:report)).map(&:to_sym)
78
+ nonblock = options.key?(:nonblock) ? options.delete(:nonblock) : false
79
+ @nonblock = nonblock
80
+ unlimited_queue = options.key?(:unlimited_queue) ? options.delete(:unlimited_queue) : false
81
+ unlimited_marks = options.key?(:unlimited_marks) ? options.delete(:unlimited_marks) : false
82
+ audit = options.key?(:audit) ? options.delete(:audit) : false
83
+ event_flags = Array(options.delete(:event_flags) || %i[rdonly largefile cloexec]).map(&:to_sym)
84
+ @on_overflow = options.delete(:on_overflow)
85
+ @on_error = options.delete(:on_error)
86
+ @on_error = @on_error.nil? ? :allow : @on_error
87
+ @on_error = @on_error.to_sym if @on_error.respond_to?(:to_sym)
88
+ @response_timeout = options.key?(:response_timeout) ? options.delete(:response_timeout) : 5.0
89
+ raise ArgumentError, "unknown options: #{options.keys.join(', ')}" unless options.empty?
90
+ unless [nonblock, unlimited_queue, unlimited_marks, audit].all? { |value| value == true || value == false }
91
+ raise ArgumentError, "boolean options must be true or false"
92
+ end
93
+ unless @on_overflow.nil? || @on_overflow.respond_to?(:call)
94
+ raise ArgumentError, "on_overflow must be callable or nil"
95
+ end
96
+ raise ArgumentError, "on_error must be :allow or :deny" unless %i[allow deny].include?(@on_error)
97
+ unless @response_timeout.nil? || (@response_timeout.is_a?(Numeric) && @response_timeout.real? &&
98
+ @response_timeout.finite? && @response_timeout.positive?)
99
+ raise ArgumentError, "response_timeout must be positive or nil"
100
+ end
101
+
102
+ @class = klass.to_sym
103
+ validate_report!
104
+ access_modes = event_flags & %i[rdonly wronly rdwr]
105
+ raise ArgumentError, "event_flags require exactly one access mode" unless access_modes.one?
106
+ flags = fetch(CLASS_FLAGS, @class, "class") | flags_for(@report, REPORT_FLAGS, "report")
107
+ flags |= FAN_NONBLOCK if nonblock
108
+ flags |= FAN_UNLIMITED_QUEUE if unlimited_queue
109
+ flags |= FAN_UNLIMITED_MARKS if unlimited_marks
110
+ flags |= FAN_ENABLE_AUDIT if audit
111
+ @handle = Native.open(flags, flags_for(event_flags, EVENT_FLAGS, "event flag"))
112
+ @pending = {}
113
+ @pending_mutex = Mutex.new
114
+ @response_mutex = Mutex.new
115
+ @readers = {}
116
+ @readers_condition = ConditionVariable.new
117
+ @closing = false
118
+ @exclude_self = false
119
+ end
120
+
121
+ def mark(action, path, events:, on: :inode, follow: true, only_dir: false)
122
+ unless [follow, only_dir].all? { |value| value == true || value == false }
123
+ raise ArgumentError, "follow and only_dir must be true or false"
124
+ end
125
+ mark = Mark.new(action: action.to_sym, path: File.path(path), events: Array(events).map(&:to_sym),
126
+ on: on.to_sym, follow:, only_dir:)
127
+ validate_mark!(mark)
128
+ flags = fetch(MARK_ACTIONS, mark.action, "mark action") | fetch(MARK_TARGETS, mark.on, "mark target")
129
+ flags |= FAN_MARK_DONT_FOLLOW unless mark.follow
130
+ flags |= FAN_MARK_ONLYDIR if mark.only_dir
131
+ @handle.mark(flags, flags_for(mark.events, EVENT_MASKS, "event"), AT_FDCWD, mark.path)
132
+ self
133
+ end
134
+
135
+ def flush(on = :inode)
136
+ @handle.mark(FAN_MARK_FLUSH | fetch(MARK_TARGETS, on.to_sym, "mark target"), 0, AT_FDCWD, nil)
137
+ self
138
+ end
139
+
140
+ def read_events(timeout: nil)
141
+ unless timeout.nil? || (timeout.is_a?(Numeric) && timeout.real? && timeout.finite? &&
142
+ timeout >= 0 && timeout * 1000 <= 2_147_483_647)
143
+ raise ArgumentError, "timeout must be a non-negative finite number or nil"
144
+ end
145
+
146
+ buffer = read_buffer(timeout)
147
+ return [] unless buffer
148
+
149
+ begin
150
+ events = Event.__send__(:parse_owned, buffer, notifier: self)
151
+ rescue Exception # Closing the group releases permission events that could not be parsed.
152
+ begin
153
+ close
154
+ rescue Exception
155
+ nil
156
+ end
157
+ raise
158
+ end
159
+ begin
160
+ events.each do |event|
161
+ next unless event.overflow?
162
+
163
+ safely_warn "fanotify event queue overflowed; events were lost"
164
+ @on_overflow&.call(event)
165
+ end
166
+ rescue Exception # SystemExit and Interrupt must not strand permission events.
167
+ events.each { |event| finish_event(event, :allow, suppress_errors: true) }
168
+ raise
169
+ end
170
+ events
171
+ end
172
+
173
+ def each_event
174
+ return enum_for(__method__) unless block_given?
175
+
176
+ loop do
177
+ events = read_events(timeout: @nonblock ? 0.1 : nil)
178
+ begin
179
+ events.each do |event|
180
+ if @exclude_self && event.pid == Process.pid
181
+ finish_event(event, :allow)
182
+ next
183
+ end
184
+
185
+ begin
186
+ yield event
187
+ ensure
188
+ handling_error = !$!.nil?
189
+ if handling_error || !event.deferred?
190
+ finish_event(event, handling_error ? @on_error : :allow, suppress_errors: handling_error)
191
+ end
192
+ end
193
+ end
194
+ ensure
195
+ events.each do |event|
196
+ finish_event(event, :allow, suppress_errors: true) unless event.closed? || event.deferred?
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ def exclude_self!
203
+ @exclude_self = true
204
+ self
205
+ end
206
+
207
+ def pending_events
208
+ @pending_mutex.synchronize do
209
+ @pending.values.filter_map { |reference, _deadline| dereference(reference) }
210
+ end
211
+ end
212
+
213
+ def to_io
214
+ @response_mutex.synchronize do
215
+ raise Error, "notifier is closed" if @closing || closed?
216
+
217
+ @io = @handle.to_io.dup unless @io && !@io.closed?
218
+ @io
219
+ end
220
+ end
221
+
222
+ def fileno = @handle.fileno
223
+ def closed? = @handle.closed?
224
+
225
+ def close
226
+ close_token = Object.new
227
+ @response_mutex.synchronize do
228
+ return if @closing || closed?
229
+
230
+ @closing = close_token
231
+ @readers.each_key do |reader|
232
+ reader.raise(Error, "notifier is closed") unless reader == Thread.current
233
+ rescue ThreadError
234
+ nil
235
+ end
236
+ end
237
+ @response_mutex.synchronize do
238
+ @readers_condition.wait(@response_mutex) while @readers.any? { |reader, _| reader != Thread.current }
239
+ end
240
+
241
+ if @watchdog && @watchdog != Thread.current
242
+ @watchdog.kill
243
+ @watchdog.join
244
+ @watchdog = nil
245
+ end
246
+ pending_events.each { |event| finish_event(event, :allow, suppress_errors: true) }
247
+ @pending_mutex.synchronize { @pending.keys }.each { |token| finalize_deferred(token) }
248
+ nil
249
+ ensure
250
+ if @closing.equal?(close_token)
251
+ active_error = $!
252
+ close_error = nil
253
+ @response_mutex.synchronize do
254
+ [@io, @handle].each do |resource|
255
+ resource&.close unless resource&.closed?
256
+ rescue StandardError => error
257
+ close_error ||= error
258
+ end
259
+ end
260
+ raise close_error if !active_error && close_error
261
+ end
262
+ end
263
+
264
+ private
265
+
266
+ def read_buffer(timeout)
267
+ reader = Thread.current
268
+ Thread.handle_interrupt(Exception => :never) do
269
+ @response_mutex.synchronize do
270
+ raise Error, "notifier is closed" if @closing || closed?
271
+
272
+ @readers[reader] = true
273
+ end
274
+ begin
275
+ Thread.handle_interrupt(Exception => :on_blocking) { @handle.read(timeout) }
276
+ ensure
277
+ @response_mutex.synchronize do
278
+ @readers.delete(reader)
279
+ @readers_condition.broadcast
280
+ end
281
+ end
282
+ end
283
+ end
284
+
285
+ def finish_event(event, decision, suppress_errors: false)
286
+ responded = false
287
+ begin
288
+ if event.permission? && !event.responded?
289
+ event.public_send("#{decision}!")
290
+ responded = true
291
+ end
292
+ ensure
293
+ event.close
294
+ end
295
+ responded
296
+ rescue StandardError => response_error
297
+ raise unless suppress_errors
298
+
299
+ safely_warn "fanotify failed to #{decision} event: #{response_error.message}"
300
+ false
301
+ end
302
+
303
+ def respond_to_event(event_fd, response)
304
+ @response_mutex.synchronize { @handle.respond(event_fd, response) }
305
+ end
306
+
307
+ def register_pending(event, event_fd, descriptors)
308
+ deadline = monotonic_time + @response_timeout if @response_timeout
309
+ reference = WeakRef.new(event)
310
+ token = Object.new
311
+ @response_mutex.synchronize do
312
+ raise Error, "notifier is closed" if @closing || closed?
313
+
314
+ @pending_mutex.synchronize do
315
+ @pending[token] = [reference, deadline, event_fd, descriptors]
316
+ end
317
+ start_watchdog
318
+ end
319
+ token
320
+ rescue Exception # Interrupt must not leave a permission event tracked but undeferred.
321
+ @pending_mutex.synchronize { @pending.delete(token) } if token
322
+ raise
323
+ end
324
+
325
+ def unregister_pending(token)
326
+ @pending_mutex.synchronize { @pending.delete(token) } if token
327
+ end
328
+
329
+ def finalize_deferred(token)
330
+ entry = @pending_mutex.synchronize { @pending.delete(token) }
331
+ return unless entry
332
+
333
+ _reference, _deadline, event_fd, descriptors = entry
334
+ @response_mutex.synchronize { @handle.respond(event_fd, FAN_ALLOW) unless closed? }
335
+ safely_warn "fanotify deferred event was garbage collected; allowing it"
336
+ rescue StandardError => error
337
+ safely_warn "fanotify failed to allow garbage-collected event: #{error.message}"
338
+ begin
339
+ close
340
+ rescue StandardError => close_error
341
+ safely_warn "fanotify failed to close after response error: #{close_error.message}"
342
+ end
343
+ ensure
344
+ descriptors&.each do |descriptor|
345
+ descriptor.close unless descriptor.closed?
346
+ rescue StandardError
347
+ nil
348
+ end
349
+ end
350
+
351
+ def start_watchdog
352
+ interval = @response_timeout ? [[@response_timeout / 10.0, 0.01].max, 1.0].min : 0.1
353
+ @pending_mutex.synchronize do
354
+ return if @watchdog&.alive?
355
+
356
+ @watchdog = Thread.new { watchdog_loop(interval) }
357
+ @watchdog.name = "fanotify-response-watchdog" if @watchdog.respond_to?(:name=)
358
+ end
359
+ end
360
+
361
+ def watchdog_loop(interval)
362
+ loop do
363
+ sleep interval
364
+ now = monotonic_time
365
+ expired, collected = @pending_mutex.synchronize do
366
+ live = []
367
+ dead = []
368
+ @pending.each do |token, (reference, deadline, _event_fd, _descriptors)|
369
+ if (event = dereference(reference))
370
+ live << event if deadline && deadline <= now
371
+ else
372
+ dead << token
373
+ end
374
+ end
375
+ [live, dead]
376
+ end
377
+ collected.each { |token| finalize_deferred(token) }
378
+ expired.each do |event|
379
+ if finish_event(event, :allow, suppress_errors: true)
380
+ safely_warn "fanotify response timeout expired; allowing event"
381
+ end
382
+ end
383
+ stop = @pending_mutex.synchronize do
384
+ @watchdog = nil if @pending.empty?
385
+ @pending.empty?
386
+ end
387
+ break if stop
388
+ end
389
+ end
390
+
391
+ def monotonic_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
392
+
393
+ def dereference(reference)
394
+ reference.__getobj__
395
+ rescue WeakRef::RefError
396
+ nil
397
+ end
398
+
399
+ def safely_warn(message)
400
+ warn message
401
+ rescue Exception # Warning failures must not kill the fail-open watchdog.
402
+ nil
403
+ end
404
+
405
+ def validate_report!
406
+ unknown = @report - REPORT_FLAGS.keys
407
+ raise ArgumentError, "unknown report flags: #{unknown.join(', ')}" unless unknown.empty?
408
+ flags = flags_for(@report, REPORT_FLAGS, "report")
409
+ if @class != :notif && (@report & %i[fid dir_fid name dfid_name target_fid]).any?
410
+ raise ArgumentError, "FID reporting requires :notif class"
411
+ end
412
+ if (@report & %i[tid pidfd]).length == 2
413
+ raise ArgumentError, ":tid and :pidfd cannot be combined"
414
+ end
415
+ if (flags & FAN_REPORT_NAME).positive? && (flags & FAN_REPORT_DIR_FID).zero?
416
+ raise ArgumentError, ":name requires :dir_fid"
417
+ end
418
+ return unless (flags & FAN_REPORT_TARGET_FID).positive? &&
419
+ (flags & FAN_REPORT_DFID_NAME) != FAN_REPORT_DFID_NAME
420
+
421
+ raise ArgumentError, ":target_fid requires :dfid_name"
422
+ end
423
+
424
+ def validate_mark!(mark)
425
+ unknown = mark.events - EVENT_MASKS.keys
426
+ raise ArgumentError, "unknown events: #{unknown.join(', ')}" unless unknown.empty?
427
+ if @class == :notif && (mark.events & PERMISSION_EVENTS).any?
428
+ raise ArgumentError, "permission events require :content or :pre_content class"
429
+ end
430
+ if mark.events.include?(:pre_access) && @class != :pre_content
431
+ raise ArgumentError, "pre_access requires :pre_content class"
432
+ end
433
+ raise ArgumentError, "overflow is receive-only" if mark.events.include?(:overflow)
434
+ if mark.events.include?(:event_on_child) && mark.on != :inode
435
+ raise ArgumentError, "event_on_child requires an inode mark"
436
+ end
437
+ if mark.events.include?(:fs_error) && (mark.on != :filesystem || !fid_reporting?)
438
+ raise ArgumentError, "fs_error requires filesystem mark and FID reporting"
439
+ end
440
+ if mark.on == :mount && (mark.events & FID_EVENTS).any?
441
+ raise ArgumentError, "#{(mark.events & FID_EVENTS).join(', ')} cannot use a mount mark"
442
+ end
443
+ return if (mark.events & FID_EVENTS).empty? || (@report & %i[fid dir_fid dfid_name]).any?
444
+
445
+ raise ArgumentError, "#{(mark.events & FID_EVENTS).join(', ')} require FID reporting"
446
+ end
447
+
448
+ def flags_for(names, mapping, label)
449
+ names.reduce(0) { |flags, name| flags | fetch(mapping, name.to_sym, label) }
450
+ end
451
+
452
+ def fid_reporting? = (flags_for(@report, REPORT_FLAGS, "report") & FAN_REPORT_FID).positive?
453
+
454
+ def fetch(mapping, name, label)
455
+ mapping.fetch(name) { raise ArgumentError, "unknown #{label}: #{name.inspect}" }
456
+ end
457
+ end
458
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fanotify
4
+ VERSION = "1.0.0"
5
+ end
data/lib/fanotify.rb ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "fanotify/version"
4
+ require "weakref"
5
+
6
+ module Fanotify
7
+ class Error < StandardError; end
8
+ class UnsupportedError < Error; end
9
+ class PermissionError < Error; end
10
+ SystemCallError = ::SystemCallError
11
+ end
12
+
13
+ require "fanotify/fanotify"
14
+
15
+ module Fanotify
16
+ Native.constants(false).grep(/^FAN|^O_|^AT_/).each { |name| const_set(name, Native.const_get(name)) }
17
+ end
18
+
19
+ require_relative "fanotify/file_handle"
20
+ require_relative "fanotify/event"
21
+ require_relative "fanotify/mark"
22
+ require_relative "fanotify/notifier"
23
+
24
+ module Fanotify
25
+ class << self
26
+ def supported? = Native.supported?
27
+
28
+ def watch(path, events: %i[create delete modify], **options, &block)
29
+ return enum_for(__method__, path, events:, **options) unless block
30
+
31
+ options = {class: :notif, report: %i[fid dfid_name]}.merge(options)
32
+ Notifier.open(**options) do |notifier|
33
+ notifier.mark(:add, path, events: events + [:event_on_child], only_dir: true)
34
+ notifier.each_event(&block)
35
+ end
36
+ end
37
+ end
38
+ end
data/sig/fanotify.rbs ADDED
@@ -0,0 +1,93 @@
1
+ module Fanotify
2
+ VERSION: String
3
+
4
+ class Error < StandardError
5
+ end
6
+
7
+ class UnsupportedError < Error
8
+ end
9
+
10
+ class PermissionError < Error
11
+ end
12
+
13
+ SystemCallError: singleton(::SystemCallError)
14
+
15
+ def self.supported?: () -> bool
16
+ def self.watch: (String | _ToPath path, ?events: Array[Symbol], **untyped options) { (Event) -> void } -> void
17
+ | (String | _ToPath path, ?events: Array[Symbol], **untyped options) -> Enumerator[Event, void]
18
+
19
+ class FileHandle
20
+ attr_reader fsid: String
21
+ attr_reader type: Integer
22
+ attr_reader bytes: String
23
+
24
+ def initialize: (fsid: String, type: Integer, bytes: String) -> void
25
+ def to_s: () -> String
26
+ end
27
+
28
+ class Mark
29
+ attr_reader action: Symbol
30
+ attr_reader path: String
31
+ attr_reader events: Array[Symbol]
32
+ attr_reader on: Symbol
33
+ attr_reader follow: bool
34
+ attr_reader only_dir: bool
35
+
36
+ def initialize: (action: Symbol, path: String, events: Array[Symbol], on: Symbol, follow: bool, only_dir: bool) -> void
37
+ end
38
+
39
+ class Event
40
+ attr_reader raw_mask: Integer
41
+ attr_reader pid: Integer?
42
+ attr_reader notifier: Notifier?
43
+ attr_reader fid: FileHandle?
44
+ attr_reader dfid: FileHandle?
45
+ attr_reader name: String?
46
+ attr_reader pidfd: Integer?
47
+ attr_reader pidfd_error: Integer?
48
+ attr_reader old_dfid: FileHandle?
49
+ attr_reader old_name: String?
50
+ attr_reader new_dfid: FileHandle?
51
+ attr_reader new_name: String?
52
+ attr_reader error: Integer?
53
+ attr_reader error_count: Integer?
54
+ attr_reader range_offset: Integer?
55
+ attr_reader range_count: Integer?
56
+
57
+ def self.parse: (String buffer) -> Array[Event]
58
+ def mask: () -> Array[Symbol]
59
+ def file: () -> IO?
60
+ def path: () -> String?
61
+ def process_path: () -> String?
62
+ def deleted?: () -> bool
63
+ def overflow?: () -> bool
64
+ def permission?: () -> bool
65
+ def responded?: () -> bool
66
+ def deferred?: () -> bool
67
+ def closed?: () -> bool
68
+ def allow!: (?audit: bool) -> self
69
+ def deny!: (?audit: bool) -> self
70
+ def defer!: () -> self
71
+ def close: () -> nil
72
+ end
73
+
74
+ class Notifier
75
+ attr_reader report: Array[Symbol]
76
+
77
+ def self.new: (**untyped options) -> Notifier
78
+ def self.open: (**untyped options) { (Notifier) -> untyped } -> untyped
79
+ | (**untyped options) -> Notifier
80
+ def initialize: (**untyped options) -> void
81
+ def mark: (Symbol action, String | _ToPath path, events: Array[Symbol], ?on: Symbol, ?follow: bool, ?only_dir: bool) -> self
82
+ def flush: (?Symbol on) -> self
83
+ def read_events: (?timeout: Numeric?) -> Array[Event]
84
+ def each_event: () { (Event) -> void } -> void
85
+ | () -> Enumerator[Event, void]
86
+ def exclude_self!: () -> self
87
+ def pending_events: () -> Array[Event]
88
+ def to_io: () -> IO
89
+ def fileno: () -> Integer
90
+ def closed?: () -> bool
91
+ def close: () -> nil
92
+ end
93
+ end
@@ -0,0 +1,59 @@
1
+ X(FAN_ACCESS)
2
+ X(FAN_MODIFY)
3
+ X(FAN_ATTRIB)
4
+ X(FAN_CLOSE_WRITE)
5
+ X(FAN_CLOSE_NOWRITE)
6
+ X(FAN_OPEN)
7
+ X(FAN_MOVED_FROM)
8
+ X(FAN_MOVED_TO)
9
+ X(FAN_CREATE)
10
+ X(FAN_DELETE)
11
+ X(FAN_DELETE_SELF)
12
+ X(FAN_MOVE_SELF)
13
+ X(FAN_OPEN_EXEC)
14
+ X(FAN_Q_OVERFLOW)
15
+ X(FAN_FS_ERROR)
16
+ X(FAN_OPEN_PERM)
17
+ X(FAN_ACCESS_PERM)
18
+ X(FAN_OPEN_EXEC_PERM)
19
+ X(FAN_PRE_ACCESS)
20
+ X(FAN_EVENT_ON_CHILD)
21
+ X(FAN_RENAME)
22
+ X(FAN_ONDIR)
23
+ X(FAN_CLOEXEC)
24
+ X(FAN_NONBLOCK)
25
+ X(FAN_CLASS_NOTIF)
26
+ X(FAN_CLASS_CONTENT)
27
+ X(FAN_CLASS_PRE_CONTENT)
28
+ X(FAN_UNLIMITED_QUEUE)
29
+ X(FAN_UNLIMITED_MARKS)
30
+ X(FAN_ENABLE_AUDIT)
31
+ X(FAN_REPORT_PIDFD)
32
+ X(FAN_REPORT_TID)
33
+ X(FAN_REPORT_FID)
34
+ X(FAN_REPORT_DIR_FID)
35
+ X(FAN_REPORT_NAME)
36
+ X(FAN_REPORT_DFID_NAME)
37
+ X(FAN_REPORT_TARGET_FID)
38
+ X(FAN_MARK_ADD)
39
+ X(FAN_MARK_REMOVE)
40
+ X(FAN_MARK_DONT_FOLLOW)
41
+ X(FAN_MARK_ONLYDIR)
42
+ X(FAN_MARK_MOUNT)
43
+ X(FAN_MARK_IGNORED_MASK)
44
+ X(FAN_MARK_IGNORED_SURV_MODIFY)
45
+ X(FAN_MARK_FLUSH)
46
+ X(FAN_MARK_FILESYSTEM)
47
+ X(FAN_MARK_EVICTABLE)
48
+ X(FAN_MARK_IGNORE)
49
+ X(FAN_MARK_INODE)
50
+ X(FAN_ALLOW)
51
+ X(FAN_DENY)
52
+ X(FAN_AUDIT)
53
+ X(FANOTIFY_METADATA_VERSION)
54
+ X(O_RDONLY)
55
+ X(O_WRONLY)
56
+ X(O_RDWR)
57
+ X(O_LARGEFILE)
58
+ X(O_CLOEXEC)
59
+ X(O_NONBLOCK)
@@ -0,0 +1,12 @@
1
+ #include <stdio.h>
2
+ #include "../ext/fanotify/compat.h"
3
+
4
+ int main(void)
5
+ {
6
+ #define X(name) printf(#name "=%llu\n", (unsigned long long)(name));
7
+ #include "constants.def"
8
+ #undef X
9
+ printf("FAN_NOFD=%d\n", FAN_NOFD);
10
+ printf("AT_FDCWD=%d\n", AT_FDCWD);
11
+ return 0;
12
+ }
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ source = <<~C
4
+ /* Generated by tools/generate_constants.rb. */
5
+ #include "ruby.h"
6
+ #include "compat.h"
7
+
8
+ void fanotify_define_constants(VALUE module)
9
+ {
10
+ #define X(name) rb_define_const(module, #name, ULL2NUM((unsigned long long)(name)));
11
+ #include "../../tools/constants.def"
12
+ #undef X
13
+ rb_define_const(module, "FAN_NOFD", INT2NUM(FAN_NOFD));
14
+ rb_define_const(module, "AT_FDCWD", INT2NUM(AT_FDCWD));
15
+ }
16
+ C
17
+
18
+ File.write(File.expand_path("../ext/fanotify/constants.c", __dir__), source)
data/tools/vm/Makefile ADDED
@@ -0,0 +1,5 @@
1
+ KVER ?= 6.12
2
+
3
+ .PHONY: test
4
+ test:
5
+ KVER=$(KVER) ./run.sh