Module: OsCtl::Lib::Utils::System

Includes:
Timeout
Included in:
Exporter::Tar, Exporter::Zfs, Zfs::Dataset, Zfs::PropertyReader, Zfs::PropertyState, Zfs::Stream, Zfs::ZpoolStatus
Defined in:
lib/libosctl/utils/system.rb

Instance Method Summary collapse

Instance Method Details

#find_executable!(cmd) ⇒ Object

Raises:

  • (Errno::ENOENT)


103
104
105
106
107
108
109
110
111
112
# File 'lib/libosctl/utils/system.rb', line 103

def find_executable!(cmd)
  ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).each do |dir|
    path = File.join(dir, cmd)
    next unless File.file?(path) && File.executable?(path)

    return File.realpath(path)
  end

  raise Errno::ENOENT, cmd
end

#read_process_output(io, wait_thr, cmd, opts) ⇒ Object (protected)



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/libosctl/utils/system.rb', line 166

def read_process_output(io, wait_thr, cmd, opts)
  if opts[:timeout]
    begin
      timeout(opts[:timeout]) { io.read }
    rescue Timeout::Error
      if opts[:on_timeout]
        opts[:on_timeout].call(wait_thr)
        ''
      else
        Process.kill('TERM', wait_thr.pid)
        raise Exceptions::SystemCommandFailed.new(cmd, 1, '')
      end
    end
  else
    io.read
  end
end

#repeat_on_failure(attempts: 3, wait: 5) { ... } ⇒ true, ...

Attempt to run a block several times

Given block is run repeatedle until it either succeeds, or the number of attempts has been reached. The block is considered successful if it does not raise any exceptions. #repeat_on_failure makes another attempt at calling the block, if it raises Exceptions::SystemCommandFailed. Any other exception will cause an immediate failure.

Parameters:

  • attempts (Integer) (defaults to: 3)

    number of attempts

  • wait (Integer) (defaults to: 5)

    time to wait after a failed attempt, in seconds

Yields:

  • the block to be called

Returns:

  • (true, any)

    return value

  • (false, Array)

    list of errors



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/libosctl/utils/system.rb', line 142

def repeat_on_failure(attempts: 3, wait: 5)
  ret = []

  attempts.times do |i|
    return [true, yield]
  rescue Exceptions::SystemCommandFailed => e
    log(:warn, "Attempt #{i + 1} of #{attempts} failed for '#{e.cmd}'")
    ret << e

    break if i == attempts - 1

    sleep(wait)
  end

  [false, ret]
end

#syscmd(cmd, opts = {}) ⇒ SystemCommandResult

Parameters:

  • cmd (String)
  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :valid_rcs (Array<Integer>, :all)

    valid exit codes

  • :stderr (Boolean)

    include stderr in output?

  • :timeout (Integer)

    in seconds

  • :on_timeout (Proc)
  • :input (String)

    data written to the process’s stdin

  • :env (Hash)

    environment variables

Returns:



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/libosctl/utils/system.rb', line 18

def syscmd(cmd, opts = {})
  valid_rcs = opts[:valid_rcs] || []
  stderr = opts[:stderr].nil? ? true : opts[:stderr]

  out = ''
  log(:work, cmd)

  IO.popen(
    opts[:env] || ENV,
    "exec #{cmd} #{stderr ? '2>&1' : '2> /dev/null'}",
    opts[:input] ? 'r+' : 'r'
  ) do |io|
    if opts[:input]
      io.write(opts[:input])
      io.close_write
    end

    if opts[:timeout]
      begin
        timeout(opts[:timeout]) do
          out = io.read
        end
      rescue Timeout::Error
        if opts[:on_timeout]
          opts[:on_timeout].call(io)

        else
          Process.kill('TERM', io.pid)
          raise Exceptions::SystemCommandFailed.new(cmd, 1, '')
        end
      end

    else
      out = io.read
    end
  end

  if $?.exitstatus != 0 && valid_rcs != :all && !valid_rcs.include?($?.exitstatus)
    raise Exceptions::SystemCommandFailed.new(cmd, $?.exitstatus, out)
  end

  SystemCommandResult.new($?.exitstatus, out)
end

#syscmd_argv(argv, opts = {}) ⇒ SystemCommandResult

Run a command without a shell.

Parameters:

  • argv (Array<String>)

    command and arguments

  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :valid_rcs (Array<Integer>, :all)

    valid exit codes

  • :stderr (Boolean)

    include stderr in output?

  • :timeout (Integer)

    in seconds

  • :on_timeout (Proc)
  • :input (String)

    data written to the process’s stdin

  • :env (Hash)

    environment variables

Returns:



73
74
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
# File 'lib/libosctl/utils/system.rb', line 73

def syscmd_argv(argv, opts = {})
  valid_rcs = opts[:valid_rcs] || []
  stderr = opts[:stderr].nil? ? true : opts[:stderr]
  cmd = argv.shelljoin
  out = ''
  status = nil

  log(:work, cmd)

  if stderr
    Open3.popen2e(opts[:env] || ENV, *argv) do |stdin, stdout_err, wait_thr|
      write_stdin(stdin, opts[:input])
      out = read_process_output(stdout_err, wait_thr, cmd, opts)
      status = wait_thr.value
    end
  else
    Open3.popen2(opts[:env] || ENV, *argv, err: File::NULL) do |stdin, stdout, wait_thr|
      write_stdin(stdin, opts[:input])
      out = read_process_output(stdout, wait_thr, cmd, opts)
      status = wait_thr.value
    end
  end

  if status.exitstatus != 0 && valid_rcs != :all && !valid_rcs.include?(status.exitstatus)
    raise Exceptions::SystemCommandFailed.new(cmd, status.exitstatus, out)
  end

  SystemCommandResult.new(status.exitstatus, out)
end

#write_stdin(io, input) ⇒ Object (protected)



161
162
163
164
# File 'lib/libosctl/utils/system.rb', line 161

def write_stdin(io, input)
  io.write(input) if input
  io.close
end

#zfs(cmd, opts, component, cmd_opts = {}) ⇒ SystemCommandResult

Parameters:

  • cmd (String)

    zfs command

  • opts (String)

    zfs options

  • component (String)

    zfs dataset

  • cmd_opts (Hash) (defaults to: {})

Options Hash (cmd_opts):

  • :valid_rcs (Array<Integer>)

    valid exit codes

  • :stderr (Boolean)

    include stderr in output?

  • :timeout (Integer)

    in seconds

  • :on_timeout (Proc)
  • :input (String)

    data written to the process’s stdin

  • :env (Hash)

    environment variables

Returns:



125
126
127
# File 'lib/libosctl/utils/system.rb', line 125

def zfs(cmd, opts, component, cmd_opts = {})
  syscmd("zfs #{cmd} #{opts} #{component}", cmd_opts)
end