seccomp-ruby 0.1.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,425 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+
5
+ module Seccomp
6
+ # Mutable seccomp filter with automatic native-resource management.
7
+ class Filter
8
+ include MonitorMixin
9
+ include Attributes
10
+ include Arg::DSL
11
+ include DSL
12
+
13
+ NOTIFICATION_MONITOR = Monitor.new
14
+ private_constant :NOTIFICATION_MONITOR
15
+
16
+ # @param default_action [Symbol, Integer] default filter action
17
+ # @return [void]
18
+ # @raise [Error] if native initialization fails
19
+ # @example `Filter.new(:allow)`
20
+ def initialize(default_action = :kill_process)
21
+ super()
22
+ @default_action = Action.resolve(default_action)
23
+ @notification_rule = @default_action == Action::NOTIFY
24
+ @listener_active = false
25
+ @notification_receive_lock = Mutex.new
26
+ @context = LowLevel.init(@default_action)
27
+ end
28
+
29
+ # @param default_action [Symbol, Integer] default action
30
+ # @yield [Filter] filter closed after the block
31
+ # @return [Object, Filter] block result, or a filter without a block
32
+ # @raise [Error] if initialization fails
33
+ # @example `Filter.open(:allow) { |filter| filter.allow(:read) }`
34
+ def self.open(default_action = :kill_process)
35
+ filter = new(default_action)
36
+ return filter unless block_given?
37
+
38
+ begin
39
+ yield filter
40
+ ensure
41
+ filter.close
42
+ end
43
+ end
44
+
45
+ # @return [nil]
46
+ # @example `filter.close`
47
+ def close
48
+ NOTIFICATION_MONITOR.synchronize do
49
+ synchronize do
50
+ LowLevel.release(@context)
51
+ @listener_active = false
52
+ nil
53
+ end
54
+ end
55
+ end
56
+
57
+ # @return [Boolean] whether native resources were released
58
+ # @example `filter.closed?`
59
+ def closed?
60
+ synchronize { LowLevel.closed?(@context) }
61
+ end
62
+
63
+ # @param default_action [Symbol, Integer, nil] replacement action
64
+ # @return [Filter] receiver
65
+ # @raise [Error] if reset fails
66
+ # @example `filter.reset(:allow)`
67
+ def reset(default_action = nil)
68
+ synchronize do
69
+ action = Action.resolve(default_action.nil? ? @default_action : default_action)
70
+ ensure_notification_tsync_supported!(notification_action: action == Action::NOTIFY)
71
+ Error.check!(LowLevel.reset(@context, action), call: "seccomp_reset")
72
+ @default_action = action
73
+ @notification_rule = action == Action::NOTIFY
74
+ end
75
+ self
76
+ end
77
+
78
+ # @param other [Filter] source consumed on success
79
+ # @return [Filter] receiver
80
+ # @raise [Error] if filters are incompatible
81
+ # @raise [TypeError] unless other is a Filter
82
+ # @example `destination.merge!(source)`
83
+ def merge!(other)
84
+ raise TypeError, "other must be a Seccomp::Filter" unless other.is_a?(Filter)
85
+ raise ArgumentError, "cannot merge a filter into itself" if equal?(other)
86
+
87
+ first, second = [self, other].sort_by(&:object_id)
88
+ first.synchronize do
89
+ second.synchronize do
90
+ notification_rule = @notification_rule || other.instance_variable_get(:@notification_rule)
91
+ if notification_rule
92
+ ensure_notification_tsync_supported!(notification_action: true,
93
+ tsync: tsync? || other.tsync?)
94
+ end
95
+ Error.check!(LowLevel.merge(@context, other.__send__(:context)), call: "seccomp_merge")
96
+ merge_state_from(other)
97
+ end
98
+ end
99
+ self
100
+ end
101
+
102
+ # @param arch [Symbol, String, Integer] architecture
103
+ # @return [Filter] receiver
104
+ # @raise [Error] if it cannot be added
105
+ # @example `filter.add_arch(:x86)`
106
+ def add_arch(arch)
107
+ change_arch(:arch_add, arch)
108
+ end
109
+
110
+ # @param arch [Symbol, String, Integer] architecture
111
+ # @return [Filter] receiver
112
+ # @raise [Error] if it cannot be removed
113
+ # @example `filter.remove_arch(:x86)`
114
+ def remove_arch(arch)
115
+ change_arch(:arch_remove, arch)
116
+ end
117
+
118
+ # @param arch [Symbol, String, Integer] architecture
119
+ # @return [Boolean] whether present
120
+ # @raise [Error] if the query fails
121
+ # @example `filter.arch?(:x86_64)`
122
+ def arch?(arch)
123
+ synchronize do
124
+ result = LowLevel.arch_exist(@context, Arch.resolve(arch))
125
+ return true if result.zero?
126
+ return false if result == -Errno::EEXIST::Errno
127
+
128
+ Error.check!(result, call: "seccomp_arch_exist", hint: "architecture #{arch}")
129
+ end
130
+ end
131
+
132
+ # @return [Array<Symbol>] configured architectures
133
+ # @raise [Error] if a query fails
134
+ # @example `filter.archs`
135
+ def archs
136
+ synchronize { Arch.all.keys.select { |arch| arch != :native && arch?(arch) } }
137
+ end
138
+
139
+ # @param action [Symbol, Integer, Array] rule action
140
+ # @param syscall [Symbol, String, Integer] syscall
141
+ # @param comparisons [Array<ArgCmp>] argument comparisons
142
+ # @return [Filter] receiver
143
+ # @raise [Error] if the rule is invalid
144
+ # @example `filter.add_rule(:allow, :write, filter.arg(0).eq(1))`
145
+ def add_rule(action, syscall, *comparisons)
146
+ add_rule_with(:rule_add_array, action, syscall, comparisons)
147
+ end
148
+
149
+ # @param action [Symbol, Integer, Array] rule action
150
+ # @param syscall [Symbol, String, Integer] syscall
151
+ # @param comparisons [Array<ArgCmp>] argument comparisons
152
+ # @return [Filter] receiver
153
+ # @raise [Error] if an exact rule cannot be represented
154
+ # @example `filter.add_rule_exact(:allow, :write)`
155
+ def add_rule_exact(action, syscall, *comparisons)
156
+ add_rule_with(:rule_add_exact_array, action, syscall, comparisons)
157
+ end
158
+
159
+ # @param syscall [Symbol, String, Integer] first syscall
160
+ # @param arguments [Array] additional syscalls followed by comparisons
161
+ # @return [Filter] receiver
162
+ # @raise [Error] if a rule is invalid
163
+ # @example `filter.allow(:read, :write)`
164
+ def allow(syscall, *arguments)
165
+ comparisons = arguments.drop_while { |argument| !argument.is_a?(ArgCmp) }
166
+ syscalls = [syscall, *arguments.take(arguments.length - comparisons.length)]
167
+ raise TypeError, "syscalls must precede comparisons" unless comparisons.all?(ArgCmp)
168
+
169
+ synchronize { syscalls.each { |name| add_rule(:allow, name, *comparisons) } }
170
+ self
171
+ end
172
+
173
+ # @param syscall [Symbol, String, Integer] syscall
174
+ # @param comparisons [Array<ArgCmp>] argument comparisons
175
+ # @param errno [Integer, SystemCallError, Class] denial errno
176
+ # @return [Filter] receiver
177
+ # @raise [Error] if the rule is invalid
178
+ # @example `filter.deny(:ptrace, errno: Errno::EPERM)`
179
+ def deny(syscall, *comparisons, errno: Errno::EPERM)
180
+ add_rule(Action.errno(errno), syscall, *comparisons)
181
+ end
182
+
183
+ # @param syscall [Symbol, String, Integer] syscall
184
+ # @param comparisons [Array<ArgCmp>] argument comparisons
185
+ # @return [Filter] receiver
186
+ # @raise [Error] if the rule is invalid
187
+ # @example `filter.kill(:ptrace)`
188
+ def kill(syscall, *comparisons)
189
+ add_rule(:kill_process, syscall, *comparisons)
190
+ end
191
+
192
+ # @param syscall [Symbol, String, Integer] syscall
193
+ # @param comparisons [Array<ArgCmp>] argument comparisons
194
+ # @return [Filter] receiver
195
+ # @raise [Error] if the rule is invalid
196
+ # @example `filter.log(:socket)`
197
+ def log(syscall, *comparisons)
198
+ add_rule(:log, syscall, *comparisons)
199
+ end
200
+
201
+ # @param syscall [Symbol, String, Integer] syscall
202
+ # @param comparisons [Array<ArgCmp>] argument comparisons
203
+ # @return [Filter] receiver
204
+ # @raise [Error] if notifications are unsupported
205
+ # @example `filter.notify(:openat)`
206
+ def notify(syscall, *comparisons)
207
+ add_rule(:notify, syscall, *comparisons)
208
+ end
209
+
210
+ # @param syscall [Symbol, String, Integer] syscall
211
+ # @param comparisons [Array<ArgCmp>] argument comparisons
212
+ # @return [Filter] receiver
213
+ # @raise [Error] if the rule is invalid
214
+ # @example `filter.trap(:ptrace)`
215
+ def trap(syscall, *comparisons)
216
+ add_rule(:trap, syscall, *comparisons)
217
+ end
218
+
219
+ # @param syscall [Symbol, String, Integer] syscall
220
+ # @param value [Integer] trace payload
221
+ # @param comparisons [Array<ArgCmp>] argument comparisons
222
+ # @return [Filter] receiver
223
+ # @raise [Error] if the rule is invalid
224
+ # @example `filter.trace(:ptrace, 42)`
225
+ def trace(syscall, value, *comparisons)
226
+ add_rule(Action.trace(value), syscall, *comparisons)
227
+ end
228
+
229
+ # @param syscall [Symbol, String, Integer] syscall
230
+ # @param value [Integer] priority from 0 through 255
231
+ # @return [Filter] receiver
232
+ # @raise [Error] if rejected
233
+ # @example `filter.priority(:write, 100)`
234
+ def priority(syscall, value)
235
+ synchronize do
236
+ Error.check!(LowLevel.syscall_priority(@context, Syscall.number(syscall), value),
237
+ call: "seccomp_syscall_priority", hint: "syscall #{syscall}")
238
+ end
239
+ self
240
+ end
241
+
242
+ # @param io [IO] writable IO
243
+ # @return [nil]
244
+ # @raise [Error] if export fails
245
+ # @example `filter.export_pfc($stdout)`
246
+ def export_pfc(io)
247
+ export_to(:export_pfc, io)
248
+ end
249
+
250
+ # @param io [IO] writable binary IO
251
+ # @return [nil]
252
+ # @raise [Error] if export fails
253
+ # @example `filter.export_bpf(file)`
254
+ def export_bpf(io)
255
+ export_to(:export_bpf, io)
256
+ end
257
+
258
+ # @return [String] pseudo filter code
259
+ # @raise [Error] if export fails
260
+ # @example `puts filter.to_pfc`
261
+ def to_pfc
262
+ export_string(:export_pfc)
263
+ end
264
+
265
+ # @return [String] binary BPF program
266
+ # @raise [Error] if export fails
267
+ # @example `filter.to_bpf.bytesize`
268
+ def to_bpf
269
+ if Seccomp.supports?(:bpf_mem)
270
+ synchronize do
271
+ rc, result = LowLevel.export_bpf_mem(@context)
272
+ Error.check!(rc, call: "seccomp_export_bpf_mem")
273
+ result
274
+ end
275
+ else
276
+ export_string(:export_bpf, binmode: true)
277
+ end
278
+ end
279
+
280
+ # @return [Filter] receiver
281
+ # @raise [NotSupportedError] when unavailable
282
+ # @example `filter.precompute`
283
+ def precompute
284
+ synchronize do
285
+ Error.check!(LowLevel.precompute(@context), call: "seccomp_precompute")
286
+ end
287
+ self
288
+ end
289
+
290
+ # @return [nil]
291
+ # @raise [Error] if the irreversible load fails
292
+ # @example `fork { filter.load!; Process.exit!(0) }`
293
+ def load!
294
+ NOTIFICATION_MONITOR.synchronize do
295
+ synchronize do
296
+ ensure_notification_tsync_supported!
297
+ if notification_action? && LowLevel.notify_fd(@context) >= 0
298
+ raise NotificationError, "a notification listener is already active"
299
+ end
300
+
301
+ Error.check!(LowLevel.load(@context), call: "seccomp_load")
302
+ @listener_active ||= @notification_rule
303
+ end
304
+ end
305
+ nil
306
+ end
307
+
308
+ # @return [Boolean] whether load succeeded at least once
309
+ # @example `filter.loaded?`
310
+ def loaded?
311
+ synchronize { LowLevel.loaded?(@context) }
312
+ end
313
+
314
+ # @yield transactional filter changes
315
+ # @return [Object] block result
316
+ # @raise [NotSupportedError] when unavailable
317
+ # @example `filter.transaction { filter.allow(:read) }`
318
+ def transaction
319
+ synchronize do
320
+ raise ArgumentError, "nested transactions are not allowed" if @in_transaction
321
+
322
+ previous_notification_rule = @notification_rule
323
+ begin
324
+ Error.check!(LowLevel.transaction_start(@context), call: "seccomp_transaction_start")
325
+ @in_transaction = true
326
+ result = yield
327
+ Error.check!(LowLevel.transaction_commit(@context), call: "seccomp_transaction_commit")
328
+ committed = true
329
+ result
330
+ ensure
331
+ LowLevel.transaction_reject(@context) if @in_transaction && !committed && !closed?
332
+ @notification_rule = previous_notification_rule unless committed
333
+ @in_transaction = false
334
+ end
335
+ end
336
+ end
337
+
338
+ # @return [Notifier] notification listener
339
+ # @raise [NotificationError] before loading or without a listener
340
+ # @example `notifier = filter.notifier`
341
+ def notifier
342
+ NOTIFICATION_MONITOR.synchronize do
343
+ synchronize do
344
+ raise NotificationError, "filter has not been loaded" unless loaded?
345
+ unless @listener_active
346
+ raise NotificationError, "filter has no active notification listener"
347
+ end
348
+
349
+ fd = LowLevel.notify_fd(@context)
350
+ if fd.negative?
351
+ raise NotificationError.new("seccomp_notify_fd failed", errno: -fd,
352
+ call: "seccomp_notify_fd")
353
+ end
354
+ Notifier.new(self, fd)
355
+ end
356
+ end
357
+ end
358
+
359
+ protected
360
+
361
+ attr_reader :context, :notification_receive_lock
362
+
363
+ private
364
+
365
+ def add_rule_with(operation, action, syscall, comparisons)
366
+ action = Action.resolve(action)
367
+ encoded = comparisons.map do |comparison|
368
+ raise TypeError, "comparison must be a Seccomp::ArgCmp" unless comparison.is_a?(ArgCmp)
369
+
370
+ comparison.to_a
371
+ end
372
+ synchronize do
373
+ ensure_notification_tsync_supported!(notification_action: true) if action == Action::NOTIFY
374
+ Error.check!(LowLevel.public_send(operation, @context, action,
375
+ Syscall.number(syscall), encoded),
376
+ call: "seccomp_#{operation}", hint: "syscall #{syscall}")
377
+ @notification_rule = true if action == Action::NOTIFY
378
+ end
379
+ self
380
+ end
381
+
382
+ def export_to(operation, io)
383
+ fd = io.fileno
384
+ synchronize do
385
+ Error.check!(LowLevel.public_send(operation, @context, fd),
386
+ call: "seccomp_#{operation}")
387
+ end
388
+ io.flush
389
+ nil
390
+ end
391
+
392
+ def notification_action? = @notification_rule
393
+
394
+ def merge_state_from(other)
395
+ @notification_rule ||= other.instance_variable_get(:@notification_rule)
396
+ @listener_active ||= other.instance_variable_get(:@listener_active)
397
+ other.instance_variable_set(:@listener_active, false)
398
+ end
399
+
400
+ def ensure_notification_tsync_supported!(notification_action: notification_action?, tsync: nil)
401
+ return unless notification_action
402
+ return unless (tsync.nil? ? tsync? : tsync) && Seccomp.api_level < 6
403
+
404
+ raise NotSupportedError, "tsync with notifications requires libseccomp API level 6"
405
+ end
406
+
407
+ def export_string(operation, binmode: false)
408
+ require "tempfile"
409
+ Tempfile.create("seccomp") do |file|
410
+ file.binmode if binmode
411
+ export_to(operation, file)
412
+ file.rewind
413
+ return file.read
414
+ end
415
+ end
416
+
417
+ def change_arch(operation, arch)
418
+ synchronize do
419
+ Error.check!(LowLevel.public_send(operation, @context, Arch.resolve(arch)),
420
+ call: "seccomp_#{operation}", hint: "architecture #{arch}")
421
+ end
422
+ self
423
+ end
424
+ end
425
+ end
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Seccomp
4
+ # Thin C-compatible API. Methods return negative errno values unless documented otherwise.
5
+ module LowLevel
6
+ # Opaque native filter context.
7
+ # rubocop:disable-next Lint/EmptyClass -- the C extension supplies its implementation.
8
+ class Context; end
9
+
10
+ # @!method self.version
11
+ # @return [Array<Integer>] major, minor, and micro version
12
+ # @example `LowLevel.version #=> [2, 6, 1]`
13
+ # @!method self.api_get
14
+ # @return [Integer] runtime API level
15
+ # @example `LowLevel.api_get`
16
+ # @!method self.api_set(level)
17
+ # @param level [Integer]
18
+ # @return [Integer] raw return code
19
+ # @raise [RangeError] for an invalid integer
20
+ # @example `LowLevel.api_set(6)`
21
+ # @!method self.supports?(feature)
22
+ # @param feature [Symbol]
23
+ # @return [Boolean]
24
+ # @example `LowLevel.supports?(:notify)`
25
+ # @!method self.init(action)
26
+ # @param action [Integer]
27
+ # @return [Context]
28
+ # @raise [OutOfMemoryError] if allocation fails
29
+ # @example `LowLevel.init(Action::ALLOW)`
30
+ # @!method self.release(context)
31
+ # @param context [Context]
32
+ # @return [nil]
33
+ # @example `LowLevel.release(context)`
34
+ # @!method self.closed?(context)
35
+ # @param context [Context]
36
+ # @return [Boolean]
37
+ # @example `LowLevel.closed?(context)`
38
+ # @!method self.reset(context, action)
39
+ # @param context [Context]
40
+ # @param action [Integer]
41
+ # @return [Integer] raw return code
42
+ # @raise [ClosedFilterError] if released
43
+ # @example `LowLevel.reset(context, Action::ALLOW)`
44
+ # @!method self.merge(destination, source)
45
+ # @param destination [Context]
46
+ # @param source [Context]
47
+ # @return [Integer] raw return code
48
+ # @raise [ClosedFilterError] if either context is released
49
+ # @example `LowLevel.merge(destination, source)`
50
+ # @!method self.attr_get(context, attribute)
51
+ # @param context [Context]
52
+ # @param attribute [Integer]
53
+ # @return [Array(Integer, Integer)] raw return code and value
54
+ # @raise [ClosedFilterError] if released
55
+ # @example `LowLevel.attr_get(context, FilterAttr::CTL_NNP)`
56
+ # @!method self.attr_set(context, attribute, value)
57
+ # @param context [Context]
58
+ # @param attribute [Integer]
59
+ # @param value [Integer]
60
+ # @return [Integer] raw return code
61
+ # @raise [ClosedFilterError] if released
62
+ # @example `LowLevel.attr_set(context, FilterAttr::CTL_NNP, 1)`
63
+ # @!method self.arch_add(context, arch)
64
+ # @param context [Context]
65
+ # @param arch [Integer]
66
+ # @return [Integer] raw return code
67
+ # @raise [ClosedFilterError] if released
68
+ # @example `LowLevel.arch_add(context, Arch::X86)`
69
+ # @!method self.arch_remove(context, arch)
70
+ # @param context [Context]
71
+ # @param arch [Integer]
72
+ # @return [Integer] raw return code
73
+ # @raise [ClosedFilterError] if released
74
+ # @example `LowLevel.arch_remove(context, Arch::X86)`
75
+ # @!method self.arch_exist(context, arch)
76
+ # @param context [Context]
77
+ # @param arch [Integer]
78
+ # @return [Integer] raw return code
79
+ # @raise [ClosedFilterError] if released
80
+ # @example `LowLevel.arch_exist(context, Arch::X86)`
81
+ # @!method self.arch_resolve_name(name)
82
+ # @param name [String]
83
+ # @return [Integer] architecture token, or zero
84
+ # @example `LowLevel.arch_resolve_name("x86_64")`
85
+ # @!method self.arch_native
86
+ # @return [Integer] native architecture token
87
+ # @example `LowLevel.arch_native`
88
+ # @!method self.syscall_resolve_name(name)
89
+ # @param name [String]
90
+ # @return [Integer] syscall number
91
+ # @example `LowLevel.syscall_resolve_name("write")`
92
+ # @!method self.syscall_resolve_name_arch(arch, name)
93
+ # @param arch [Integer]
94
+ # @param name [String]
95
+ # @return [Integer] syscall number
96
+ # @example `LowLevel.syscall_resolve_name_arch(Arch::X86, "write")`
97
+ # @!method self.syscall_resolve_name_rewrite(arch, name)
98
+ # @param arch [Integer]
99
+ # @param name [String]
100
+ # @return [Integer] rewritten syscall number
101
+ # @example `LowLevel.syscall_resolve_name_rewrite(Arch::X86, "socket")`
102
+ # @!method self.syscall_resolve_num_arch(arch, number)
103
+ # @param arch [Integer]
104
+ # @param number [Integer]
105
+ # @return [String, nil] syscall name
106
+ # @example `LowLevel.syscall_resolve_num_arch(Arch::X86_64, 1)`
107
+ # @!method self.rule_add_array(context, action, syscall, comparisons)
108
+ # @param context [Context]
109
+ # @param action [Integer]
110
+ # @param syscall [Integer]
111
+ # @param comparisons [Array<Array<Integer>>]
112
+ # @return [Integer] raw return code
113
+ # @raise [ArgumentError] for malformed comparisons
114
+ # @example `LowLevel.rule_add_array(context, Action::ALLOW, 1, [])`
115
+ # @!method self.rule_add_exact_array(context, action, syscall, comparisons)
116
+ # @param context [Context]
117
+ # @param action [Integer]
118
+ # @param syscall [Integer]
119
+ # @param comparisons [Array<Array<Integer>>]
120
+ # @return [Integer] raw return code
121
+ # @raise [ArgumentError] for malformed comparisons
122
+ # @example `LowLevel.rule_add_exact_array(context, Action::ALLOW, 1, [])`
123
+ # @!method self.syscall_priority(context, syscall, priority)
124
+ # @param context [Context]
125
+ # @param syscall [Integer]
126
+ # @param priority [Integer]
127
+ # @return [Integer] raw return code
128
+ # @raise [RangeError] outside 0..255
129
+ # @example `LowLevel.syscall_priority(context, 1, 100)`
130
+ # @!method self.export_pfc(context, fd)
131
+ # @param context [Context]
132
+ # @param fd [Integer]
133
+ # @return [Integer] raw return code
134
+ # @raise [ClosedFilterError] if released
135
+ # @example `LowLevel.export_pfc(context, io.fileno)`
136
+ # @!method self.export_bpf(context, fd)
137
+ # @param context [Context]
138
+ # @param fd [Integer]
139
+ # @return [Integer] raw return code
140
+ # @raise [ClosedFilterError] if released
141
+ # @example `LowLevel.export_bpf(context, io.fileno)`
142
+ # @!method self.export_bpf_mem(context)
143
+ # @param context [Context]
144
+ # @return [Array(Integer, String)] raw return code and BPF
145
+ # @raise [NotSupportedError] when unavailable
146
+ # @example `LowLevel.export_bpf_mem(context)`
147
+ # @!method self.precompute(context)
148
+ # @param context [Context]
149
+ # @return [Integer] raw return code
150
+ # @raise [NotSupportedError] when unavailable
151
+ # @example `LowLevel.precompute(context)`
152
+ # @!method self.load(context)
153
+ # @param context [Context]
154
+ # @return [Integer] raw return code
155
+ # @raise [ClosedFilterError] if released
156
+ # @example `fork { LowLevel.load(context) }`
157
+ # @!method self.loaded?(context)
158
+ # @param context [Context]
159
+ # @return [Boolean]
160
+ # @raise [ClosedFilterError] if released
161
+ # @example `LowLevel.loaded?(context)`
162
+ # @!method self.transaction_start(context)
163
+ # @param context [Context]
164
+ # @return [Integer] raw return code
165
+ # @raise [NotSupportedError] when unavailable
166
+ # @example `LowLevel.transaction_start(context)`
167
+ # @!method self.transaction_commit(context)
168
+ # @param context [Context]
169
+ # @return [Integer] raw return code
170
+ # @raise [NotSupportedError] when unavailable
171
+ # @example `LowLevel.transaction_commit(context)`
172
+ # @!method self.transaction_reject(context)
173
+ # @param context [Context]
174
+ # @return [Integer] zero
175
+ # @raise [NotSupportedError] when unavailable
176
+ # @example `LowLevel.transaction_reject(context)`
177
+ # @!method self.notify_fd(context)
178
+ # @param context [Context]
179
+ # @return [Integer] listener fd or negative errno
180
+ # @raise [ClosedFilterError] if released
181
+ # @example `LowLevel.notify_fd(context)`
182
+ # @!method self.notify_receive(fd)
183
+ # @param fd [Integer]
184
+ # @return [Array(Integer, Hash)] raw return code and request
185
+ # @example `LowLevel.notify_receive(fd)`
186
+ # @!method self.notify_respond(fd, id, value, error, flags)
187
+ # @param fd [Integer]
188
+ # @param id [Integer]
189
+ # @param value [Integer]
190
+ # @param error [Integer]
191
+ # @param flags [Integer]
192
+ # @return [Integer] raw return code
193
+ # @example `LowLevel.notify_respond(fd, id, 0, 0, 0)`
194
+ # @!method self.notify_id_valid(fd, id)
195
+ # @param fd [Integer]
196
+ # @param id [Integer]
197
+ # @return [Integer] raw return code
198
+ # @example `LowLevel.notify_id_valid(fd, id)`
199
+ # @!method self.notify_addfd(fd, id, flags, source_fd, newfd, newfd_flags)
200
+ # @param fd [Integer]
201
+ # @param id [Integer]
202
+ # @param flags [Integer]
203
+ # @param source_fd [Integer]
204
+ # @param newfd [Integer]
205
+ # @param newfd_flags [Integer]
206
+ # @return [Integer] installed fd or negative errno
207
+ # @raise [NotSupportedError] when unavailable
208
+ # @example `LowLevel.notify_addfd(fd, id, 0, source.fileno, 0, 0)`
209
+ end
210
+ end