Class: Seccomp::Notify::Supervisor

Inherits:
Object
  • Object
show all
Defined in:
lib/seccomp/notify/supervisor.rb

Constant Summary collapse

USER_OPTIONS =
%i[poll_interval default_errno timeout concurrency features].freeze
EXPECTED_SIZES =
{notif: Structs::NOTIF_SIZE, resp: Structs::RESPONSE_SIZE, data: 64}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(listener, target_pid: nil, target_child: nil, poll_interval: 0.1, default_errno: Errno::EPERM, timeout: nil, concurrency: 1, features: Notify.features, health_writer: nil) ⇒ Supervisor

Returns a new instance of Supervisor.

Raises:



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/seccomp/notify/supervisor.rb', line 37

def initialize(listener, target_pid: nil, target_child: nil, poll_interval: 0.1, default_errno: Errno::EPERM,
  timeout: nil, concurrency: 1, features: Notify.features, health_writer: nil)
  self.class.validate_options!(poll_interval:, default_errno:, timeout:, concurrency:, features:)

  @listener = listener
  @target_pid = target_pid
  @target_child = target_child
  @poll_interval = poll_interval
  @default_errno = default_errno
  @timeout = timeout
  @concurrency = concurrency
  @features = features
  @health_writer = health_writer
  @sizes = Notify.notif_sizes
  raise NotSupportedError, "unsupported kernel notification sizes: #{@sizes.inspect}" unless @sizes == EXPECTED_SIZES

  @handlers = {}
  @unknown_handler = ->(request) { request.error!(@default_errno) }
  @error_handler = ->(error, _request) { warn("seccomp-notify supervisor: #{error.full_message(highlight: false, order: :top)}") }
  @receive_mutex = Mutex.new
  @stop = false
  @listener_closed = false
end

Instance Attribute Details

#listenerObject (readonly)

Returns the value of attribute listener.



11
12
13
# File 'lib/seccomp/notify/supervisor.rb', line 11

def listener
  @listener
end

#target_pidObject (readonly)

Returns the value of attribute target_pid.



11
12
13
# File 'lib/seccomp/notify/supervisor.rb', line 11

def target_pid
  @target_pid
end

Class Method Details

.valid_duration?(value, allow_zero:) ⇒ Boolean

Returns:

  • (Boolean)


33
34
35
# File 'lib/seccomp/notify/supervisor.rb', line 33

def self.valid_duration?(value, allow_zero:)
  value.is_a?(Numeric) && value.real? && value.finite? && (allow_zero ? !value.negative? : value.positive?)
end

.validate_options!(options) ⇒ Object

Raises:

  • (ArgumentError)


13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/seccomp/notify/supervisor.rb', line 13

def self.validate_options!(options)
  unknown = options.keys - USER_OPTIONS
  raise ArgumentError, "unknown supervisor options: #{unknown.join(", ")}" unless unknown.empty?
  if options.key?(:concurrency) && (!options[:concurrency].is_a?(Integer) || !options[:concurrency].positive?)
    raise ArgumentError, "concurrency must be a positive integer"
  end
  if options.key?(:poll_interval) && !valid_duration?(options[:poll_interval], allow_zero: true)
    raise ArgumentError, "poll_interval must be non-negative"
  end
  if options[:timeout] && !valid_duration?(options[:timeout], allow_zero: false)
    raise ArgumentError, "timeout must be positive"
  end
  if options.key?(:features) && !options[:features].is_a?(Hash)
    raise ArgumentError, "features must be a hash"
  end
  return unless options.key?(:default_errno)

  Constants.errno_number(options[:default_errno])
end

Instance Method Details

#on(syscall) {|request| ... } ⇒ Supervisor

Registers a handler for a notified syscall.

Parameters:

  • syscall (Symbol)

Yield Parameters:

Returns:

Raises:

  • (ArgumentError)


65
66
67
68
69
70
# File 'lib/seccomp/notify/supervisor.rb', line 65

def on(syscall, &handler)
  raise ArgumentError, "handler block is required" unless handler

  @handlers[syscall.to_sym] = handler
  self
end

#on_error {|error, request| ... } ⇒ Supervisor

Registers an exception callback.

Yield Parameters:

  • error (Exception)
  • request (Request)

Returns:

Raises:

  • (ArgumentError)


86
87
88
89
90
91
# File 'lib/seccomp/notify/supervisor.rb', line 86

def on_error(&handler)
  raise ArgumentError, "handler block is required" unless handler

  @error_handler = handler
  self
end

#on_unknown {|request| ... } ⇒ Supervisor

Registers the fallback handler for unregistered syscalls.

Yield Parameters:

Returns:

Raises:

  • (ArgumentError)


75
76
77
78
79
80
# File 'lib/seccomp/notify/supervisor.rb', line 75

def on_unknown(&handler)
  raise ArgumentError, "handler block is required" unless handler

  @unknown_handler = handler
  self
end

#runProcess::Status?

Runs until the target exits and returns its process status when available.

Returns:

  • (Process::Status, nil)


95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/seccomp/notify/supervisor.rb', line 95

def run
  workers = Array.new(@concurrency) { Thread.new { event_loop } }
  status = wait_for_target
  wait_until_listener_closes if @target_pid && !@stop
  @stop = true
  workers.each(&:join)
  status
ensure
  @stop = true
  @listener.close unless @listener.closed?
  @health_writer&.close unless @health_writer&.closed?
end

#stopObject



108
109
110
# File 'lib/seccomp/notify/supervisor.rb', line 108

def stop
  @stop = true
end