Class: Fanotify::Notifier
- Inherits:
-
Object
- Object
- Fanotify::Notifier
- Defined in:
- lib/fanotify/notifier.rb
Overview
Owns a fanotify group and provides safe event iteration.
Constant Summary collapse
- PERMISSION_EVENTS =
%i[open_perm access_perm open_exec_perm pre_access].freeze
- PERMISSION_MASK =
PERMISSION_EVENTS.reduce(0) { |mask, event| mask | EVENT_MASKS.fetch(event) }
- FID_EVENTS =
%i[attrib create delete delete_self moved_from moved_to move_self rename].freeze
- MARK_ACTIONS =
{add: FAN_MARK_ADD, remove: FAN_MARK_REMOVE}.freeze
- MARK_TARGETS =
{inode: FAN_MARK_INODE, mount: FAN_MARK_MOUNT, filesystem: FAN_MARK_FILESYSTEM}.freeze
Instance Attribute Summary collapse
-
#report ⇒ Object
readonly
Returns the value of attribute report.
Class Method Summary collapse
Instance Method Summary collapse
- #close ⇒ Object
- #closed? ⇒ Boolean
- #each_event ⇒ Object
- #exclude_self! ⇒ Object
- #fileno ⇒ Object
- #flush(on = :inode) ⇒ Object
-
#initialize(**options) ⇒ Notifier
constructor
A new instance of Notifier.
- #mark(action, path, events:, on: :inode, follow: true, only_dir: false) ⇒ Object
- #pending_events ⇒ Object
- #read_events(timeout: nil) ⇒ Object
- #to_io ⇒ Object
Constructor Details
#initialize(**options) ⇒ Notifier
Returns a new instance of Notifier.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/fanotify/notifier.rb', line 75 def initialize(**) klass = .delete(:class) || :notif @report = Array(.delete(:report)).map(&:to_sym) nonblock = .key?(:nonblock) ? .delete(:nonblock) : false @nonblock = nonblock unlimited_queue = .key?(:unlimited_queue) ? .delete(:unlimited_queue) : false unlimited_marks = .key?(:unlimited_marks) ? .delete(:unlimited_marks) : false audit = .key?(:audit) ? .delete(:audit) : false event_flags = Array(.delete(:event_flags) || %i[rdonly largefile cloexec]).map(&:to_sym) @on_overflow = .delete(:on_overflow) @on_error = .delete(:on_error) @on_error = @on_error.nil? ? :allow : @on_error @on_error = @on_error.to_sym if @on_error.respond_to?(:to_sym) @response_timeout = .key?(:response_timeout) ? .delete(:response_timeout) : 5.0 raise ArgumentError, "unknown options: #{.keys.join(', ')}" unless .empty? unless [nonblock, unlimited_queue, unlimited_marks, audit].all? { |value| value == true || value == false } raise ArgumentError, "boolean options must be true or false" end unless @on_overflow.nil? || @on_overflow.respond_to?(:call) raise ArgumentError, "on_overflow must be callable or nil" end raise ArgumentError, "on_error must be :allow or :deny" unless %i[allow deny].include?(@on_error) unless @response_timeout.nil? || (@response_timeout.is_a?(Numeric) && @response_timeout.real? && @response_timeout.finite? && @response_timeout.positive?) raise ArgumentError, "response_timeout must be positive or nil" end @class = klass.to_sym validate_report! access_modes = event_flags & %i[rdonly wronly rdwr] raise ArgumentError, "event_flags require exactly one access mode" unless access_modes.one? flags = fetch(CLASS_FLAGS, @class, "class") | flags_for(@report, REPORT_FLAGS, "report") flags |= FAN_NONBLOCK if nonblock flags |= FAN_UNLIMITED_QUEUE if unlimited_queue flags |= FAN_UNLIMITED_MARKS if unlimited_marks flags |= FAN_ENABLE_AUDIT if audit @handle = Native.open(flags, flags_for(event_flags, EVENT_FLAGS, "event flag")) @pending = {} @pending_mutex = Mutex.new @response_mutex = Mutex.new @readers = {} @readers_condition = ConditionVariable.new @closing = false @exclude_self = false end |
Instance Attribute Details
#report ⇒ Object (readonly)
Returns the value of attribute report.
62 63 64 |
# File 'lib/fanotify/notifier.rb', line 62 def report @report end |
Class Method Details
.open(**options) ⇒ Object
64 65 66 67 68 69 70 71 72 73 |
# File 'lib/fanotify/notifier.rb', line 64 def self.open(**) notifier = new(**) return notifier unless block_given? begin yield notifier ensure notifier.close end end |
Instance Method Details
#close ⇒ Object
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/fanotify/notifier.rb', line 225 def close close_token = Object.new @response_mutex.synchronize do return if @closing || closed? @closing = close_token @readers.each_key do |reader| reader.raise(Error, "notifier is closed") unless reader == Thread.current rescue ThreadError nil end end @response_mutex.synchronize do @readers_condition.wait(@response_mutex) while @readers.any? { |reader, _| reader != Thread.current } end if @watchdog && @watchdog != Thread.current @watchdog.kill @watchdog.join @watchdog = nil end pending_events.each { |event| finish_event(event, :allow, suppress_errors: true) } @pending_mutex.synchronize { @pending.keys }.each { |token| finalize_deferred(token) } nil ensure if @closing.equal?(close_token) active_error = $! close_error = nil @response_mutex.synchronize do [@io, @handle].each do |resource| resource&.close unless resource&.closed? rescue StandardError => error close_error ||= error end end raise close_error if !active_error && close_error end end |
#closed? ⇒ Boolean
223 |
# File 'lib/fanotify/notifier.rb', line 223 def closed? = @handle.closed? |
#each_event ⇒ Object
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/fanotify/notifier.rb', line 173 def each_event return enum_for(__method__) unless block_given? loop do events = read_events(timeout: @nonblock ? 0.1 : nil) begin events.each do |event| if @exclude_self && event.pid == Process.pid finish_event(event, :allow) next end begin yield event ensure handling_error = !$!.nil? if handling_error || !event.deferred? finish_event(event, handling_error ? @on_error : :allow, suppress_errors: handling_error) end end end ensure events.each do |event| finish_event(event, :allow, suppress_errors: true) unless event.closed? || event.deferred? end end end end |
#exclude_self! ⇒ Object
202 203 204 205 |
# File 'lib/fanotify/notifier.rb', line 202 def exclude_self! @exclude_self = true self end |
#fileno ⇒ Object
222 |
# File 'lib/fanotify/notifier.rb', line 222 def fileno = @handle.fileno |
#flush(on = :inode) ⇒ Object
135 136 137 138 |
# File 'lib/fanotify/notifier.rb', line 135 def flush(on = :inode) @handle.mark(FAN_MARK_FLUSH | fetch(MARK_TARGETS, on.to_sym, "mark target"), 0, AT_FDCWD, nil) self end |
#mark(action, path, events:, on: :inode, follow: true, only_dir: false) ⇒ Object
121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/fanotify/notifier.rb', line 121 def mark(action, path, events:, on: :inode, follow: true, only_dir: false) unless [follow, only_dir].all? { |value| value == true || value == false } raise ArgumentError, "follow and only_dir must be true or false" end mark = Mark.new(action: action.to_sym, path: File.path(path), events: Array(events).map(&:to_sym), on: on.to_sym, follow:, only_dir:) validate_mark!(mark) flags = fetch(MARK_ACTIONS, mark.action, "mark action") | fetch(MARK_TARGETS, mark.on, "mark target") flags |= FAN_MARK_DONT_FOLLOW unless mark.follow flags |= FAN_MARK_ONLYDIR if mark.only_dir @handle.mark(flags, flags_for(mark.events, EVENT_MASKS, "event"), AT_FDCWD, mark.path) self end |
#pending_events ⇒ Object
207 208 209 210 211 |
# File 'lib/fanotify/notifier.rb', line 207 def pending_events @pending_mutex.synchronize do @pending.values.filter_map { |reference, _deadline| dereference(reference) } end end |
#read_events(timeout: nil) ⇒ Object
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/fanotify/notifier.rb', line 140 def read_events(timeout: nil) unless timeout.nil? || (timeout.is_a?(Numeric) && timeout.real? && timeout.finite? && timeout >= 0 && timeout * 1000 <= 2_147_483_647) raise ArgumentError, "timeout must be a non-negative finite number or nil" end buffer = read_buffer(timeout) return [] unless buffer begin events = Event.__send__(:parse_owned, buffer, notifier: self) rescue Exception # Closing the group releases permission events that could not be parsed. begin close rescue Exception nil end raise end begin events.each do |event| next unless event.overflow? safely_warn "fanotify event queue overflowed; events were lost" @on_overflow&.call(event) end rescue Exception # SystemExit and Interrupt must not strand permission events. events.each { |event| finish_event(event, :allow, suppress_errors: true) } raise end events end |
#to_io ⇒ Object
213 214 215 216 217 218 219 220 |
# File 'lib/fanotify/notifier.rb', line 213 def to_io @response_mutex.synchronize do raise Error, "notifier is closed" if @closing || closed? @io = @handle.to_io.dup unless @io && !@io.closed? @io end end |