module Kanrisuru::Core::System

Constants

CPUArchitecture
CPUArchitectureVulnerability
KernelStatistic
KernelStatisticCpu
LoginUser
OpenFile
ProcessInfo
SessionDetail
Uptime
UserLoggedIn

Public Instance Methods

cpu_info(spec) click to toggle source
# File lib/kanrisuru/core/system.rb, line 237
def cpu_info(spec)
  Kanrisuru.logger.info do
    'DEPRECATION WARNING: cpu_info will be removed in the upcoming major release. Use lscpu instead.'
  end

  name =
    case spec
    when 'sockets'
      '^Socket'
    when 'cores_per_socket'
      '^Core'
    when 'threads'
      '^Thread'
    when 'cores'
      '^CPU('
    else
      return
    end

  command = Kanrisuru::Command.new("lscpu | grep -i '#{name}' | awk '{print $NF}'")
  execute(command)

  Kanrisuru::Result.new(command, &:to_i)
end
free(type) click to toggle source
# File lib/kanrisuru/core/system.rb, line 271
def free(type)
  conversions = {
    total: 'MemTotal',
    free: 'MemFree',
    swap: 'SwapTotal',
    swap_free: 'SwapFree'
  }

  option = conversions[type.to_sym]
  raise ArgumentError, 'Invalid mem type' unless option

  command = Kanrisuru::Command.new("cat /proc/meminfo | grep -i '^#{option}' | awk '{print $2}'")
  execute(command)

  ## In kB
  Kanrisuru::Result.new(command, &:to_i)
end
kernel_statistics() click to toggle source
# File lib/kanrisuru/core/system.rb, line 451
def kernel_statistics
  command = Kanrisuru::Command.new('cat /proc/stat')

  execute_shell(command)

  Kanrisuru::Result.new(command) do |cmd|
    lines = cmd.to_a

    result = KernelStatistic.new
    result.cpus = []

    lines.each do |line|
      values = line.split
      field = values[0]
      values = values[1..-1].map(&:to_i)

      case field
      when /^cpu/
        cpu_stat = KernelStatisticCpu.new
        cpu_stat.user = values[0]
        cpu_stat.nice = values[1]
        cpu_stat.system = values[2]
        cpu_stat.idle = values[3]
        cpu_stat.iowait = values[4]
        cpu_stat.irq = values[5]
        cpu_stat.softirq = values[6]
        cpu_stat.steal = values[7]
        cpu_stat.guest = values[8]
        cpu_stat.guest_nice = values[9]

        case field
        when /^cpu$/
          result.cpu_total = cpu_stat
        when /^cpu\d+/
          result.cpus << cpu_stat
        end
      when 'intr'
        result.interrupt_total = values[0]
        result.interrupts = values[1..-1]
      when 'softirq'
        result.softirq_total = values[0]
        result.softirqs = values[1..-1]
      else
        result[field] = values[0]
      end
    end

    result
  end
end
kill(signal, pids) click to toggle source
# File lib/kanrisuru/core/system.rb, line 382
def kill(signal, pids)
  raise ArgumentError, 'Invalid signal' unless Kanrisuru::Util::Signal.valid?(signal)

  ## Use named signals for readabilitiy
  signal = Kanrisuru::Util::Signal[signal] if signal.instance_of?(Integer)

  command = Kanrisuru::Command.new('kill')
  command << "-#{signal}"
  command << (pids.instance_of?(Array) ? pids.join(' ') : pids)

  execute_shell(command)

  Kanrisuru::Result.new(command)
end
kstat() click to toggle source
# File lib/kanrisuru/core/system.rb, line 447
def kstat
  kernel_statistics
end
last(opts = {}) click to toggle source
# File lib/kanrisuru/core/system.rb, line 289
def last(opts = {})
  command =
    if opts[:failed_attempts]
      Kanrisuru::Command.new('lastb')
    else
      Kanrisuru::Command.new('last')
    end

  command.append_flag('-i')
  command.append_flag('-F')
  command.append_arg('-f', opts[:file])

  ## Some systems only use 1 space between user and TTY field
  ## Add an additional space in output formatting for simple parsing
  ## logic.
  command | "sed 's/ /  /'"

  execute_shell(command)

  Kanrisuru::Result.new(command) do |cmd|
    lines = cmd.to_a

    mapping = {}

    lines.each do |line|
      next if Kanrisuru::Util.blank?(line)
      next if line.include?('wtmp') || line.include?('btmp')

      line = line.gsub('  still logged in', '- still logged in') if line.include?('still logged in')

      values = line.split(/\s{2,}/, 4)
      user   = values[0]
      tty    = values[1]
      ip     = IPAddr.new(values[2])

      date_range = values[3]
      login, logout = date_range.split(' - ')

      login = parse_last_date(login) if login

      logout = parse_last_date(logout) if logout

      detail = SessionDetail.new
      detail.tty = tty
      detail.ip_address = ip
      detail.login_at = login
      detail.logout_at = logout

      detail.success = !Kanrisuru::Util.present?(opts[:failed_attemps])

      mapping[user] = LoginUser.new(user, []) unless mapping.key?(user)

      mapping[user].sessions << detail
    end

    mapping.values
  end
end
load_average() click to toggle source
# File lib/kanrisuru/core/system.rb, line 262
def load_average
  command = Kanrisuru::Command.new("cat /proc/loadavg | awk '{print $1,$2,$3}'")
  execute(command)

  Kanrisuru::Result.new(command) do |cmd|
    cmd.to_s.split.map(&:to_f)
  end
end
load_env() click to toggle source
# File lib/kanrisuru/core/system.rb, line 134
def load_env
  command = Kanrisuru::Command.new('env')
  execute_shell(command)

  Kanrisuru::Result.new(command) do |cmd|
    string = cmd.to_s
    hash = {}

    rows = string.split("\n")
    rows.each do |row|
      key, value = row.split('=', 2)
      hash[key] = value
    end

    hash
  end
end
lscpu() click to toggle source
# File lib/kanrisuru/core/system.rb, line 152
def lscpu
  command = Kanrisuru::Command.new('lscpu')

  execute_shell(command)

  Kanrisuru::Result.new(command) do |cmd|
    lines = cmd.to_a

    result = CPUArchitecture.new
    result.vulnerabilities = []
    result.numa_nodes = []

    lines.each do |line|
      values = line.split(': ', 2)

      field = values[0].strip
      data = values[1].strip

      case field
      when 'Architecture'
        result.architecture = data
      when 'CPU op-mode(s)'
        result.operation_modes = data.split(', ')
      when 'Byte Order'
        result.byte_order = data
      when 'Address sizes'
        result.address_sizes = data.split(', ')
      when 'CPU(s)'
        result.cores = data.to_i
      when 'On-line CPU(s) list'
        result.online_cpus = data.to_i
      when 'Thread(s) per core'
        result.threads_per_core = data.to_i
      when 'Core(s) per socket'
        result.cores_per_socket = data.to_i
      when 'Socket(s)'
        result.sockets = data.to_i
      when 'NUMA node(s)'
        result.numa_nodes = data.to_i
      when 'Vendor ID'
        result.vendor_id = data
      when 'CPU family'
        result.cpu_family = data.to_i
      when 'Model'
        result.model = data.to_i
      when 'Model name'
        result.model_name = data
      when 'Stepping'
        result.stepping = data.to_i
      when 'CPU MHz'
        result.cpu_mhz = data.to_f
      when 'CPU max MHz'
        result.cpu_max_mhz = data.to_f
      when 'CPU min MHz'
        result.cpu_min_mhz = data.to_f
      when 'CPUBogoMIPS'
        result.bogo_mips = data.to_f
      when 'Virtualization'
        result.virtualization = data
      when 'Hypervisor vendor'
        result.hypervisor_vendor = data
      when 'Virtualization type'
        result.virtualization_type = data
      when 'L1d cache'
        result.l1d_cache = data
      when 'L1i cache'
        result.l1i_cache = data
      when 'L2 cache'
        result.l2_cache = data
      when 'L3 cache'
        result.l3_cache = data
      when /^Numa node/
        result.numa_nodes << data.split(',')
      when /^Vulnerability/
        name = field.split('Vulnerability ')[1]
        result.vulnerabilities << CPUArchitectureVulnerability.new(name, data)
      when 'Flags'
        result.flags = data.split
      end
    end

    result
  end
end
lsof(_opts = {}) click to toggle source
# File lib/kanrisuru/core/system.rb, line 397
def lsof(_opts = {})
  command = Kanrisuru::Command.new('lsof -F pcuftDsin')

  execute_shell(command)
  Kanrisuru::Result.new(command) do |cmd|
    lines = cmd.to_a

    current_row = nil
    current_pid = nil
    current_user = nil
    current_command = nil

    rows = []

    lines.each do |line|
      case line
      when /^p/
        current_pid = parse_lsof(line, 'p').to_i
      when /^c/
        current_command = parse_lsof(line, 'c')
      when /^u/
        current_user = parse_lsof(line, 'u').to_i
      when /^f/
        rows << current_row if current_row

        current_row = OpenFile.new
        current_row.pid = current_pid
        current_row.command = current_command
        current_row.uid = current_user

        current_row.file_descriptor = parse_lsof(line, 'f')
      when /^t/
        current_row.type = parse_lsof(line, 't')
      when /^D/
        current_row.device = parse_lsof(line, 'D')
      when /^s/
        current_row.fsize = parse_lsof(line, 's').to_i
      when /^i/
        current_row.inode = parse_lsof(line, 'i').to_i
      when /^n/
        current_row.name = parse_lsof(line, 'n')
      end
    end

    rows << current_row if current_row

    rows
  end
end
poweroff(opts = {}) click to toggle source
# File lib/kanrisuru/core/system.rb, line 603
def poweroff(opts = {})
  time = opts[:time]
  cancel = opts[:cancel]
  message = opts[:message]
  no_wall = opts[:no_wall]

  command = Kanrisuru::Command.new('shutdown')

  if Kanrisuru::Util.present?(cancel)
    command.append_flag('-c')
    command << message if message
  else
    time = format_shutdown_time(time) if Kanrisuru::Util.present?(time)

    if Kanrisuru::Util.present?(no_wall)
      command.append_flag('--no-wall')
    else
      command << time if time
      command << message if message
    end
  end

  begin
    execute_shell(command)

    Kanrisuru::Result.new(command)
  rescue IOError
    ## When powering off 'now', ssh io stream closes
    ## Set exit status to 0
    command.handle_status(0)

    Kanrisuru::Result.new(command)
  end
end
ps(opts = {}) click to toggle source
# File lib/kanrisuru/core/system.rb, line 348
def ps(opts = {})
  group = opts[:group]
  user  = opts[:user]
  pid   = opts[:pid]
  ppid  = opts[:ppid]
  # tree  = opts[:tree]

  command = Kanrisuru::Command.new('ps ww')

  if !user.nil? || !group.nil? || !pid.nil? || !ppid.nil?
    command.append_arg('--user', user.instance_of?(Array) ? user.join(',') : user)
    command.append_arg('--group', group.instance_of?(Array) ? group.join(',') : group)
    command.append_arg('--pid', pid.instance_of?(Array) ? pid.join(',') : pid)
    command.append_arg('--ppid', ppid.instance_of?(Array) ? ppid.join(',') : ppid)
  else
    command.append_flag('ax')
  end

  command.append_arg('-o', 'uid,user,gid,group,ppid,pid,pcpu,pmem,stat,pri,flags,policy,time,cmd')
  command.append_flag('--no-headers')

  execute(command)

  ## Have found issues with regular newline parsing from command
  ## most likely a buffer overflow from SSH buffer.
  ## Join string then split by newline.
  Kanrisuru::Result.new(command) do |cmd|
    result_string = cmd.raw_result.join
    rows = result_string.split("\n")

    build_process_list(rows)
  end
end
reboot(opts = {}) click to toggle source
# File lib/kanrisuru/core/system.rb, line 568
def reboot(opts = {})
  time = opts[:time]
  cancel = opts[:cancel]
  message = opts[:message]
  no_wall = opts[:no_wall]

  command = Kanrisuru::Command.new('shutdown')

  if Kanrisuru::Util.present?(cancel)
    command.append_flag('-c')
    command << message if message
  else
    command.append_flag('-r')

    time = format_shutdown_time(time) if Kanrisuru::Util.present?(time)

    if Kanrisuru::Util.present?(no_wall)
      command.append_flag('--no-wall')
    else
      command << time if time
      command << message if message
    end
  end

  begin
    execute_shell(command)
    Kanrisuru::Result.new(command)
  rescue IOError
    ## When rebooting 'now', ssh io stream closes
    ## Set exit status to 0
    command.handle_status(0)
    Kanrisuru::Result.new(command)
  end
end
uptime() click to toggle source
# File lib/kanrisuru/core/system.rb, line 502
def uptime
  ## Ported from https://github.com/djberg96/sys-uptime
  command = Kanrisuru::Command.new('cat /proc/uptime')

  execute_shell(command)

  Kanrisuru::Result.new(command) do |cmd|
    seconds = cmd.to_s.split[0].to_i
    minutes = seconds / 60
    hours = seconds / 3600
    days = seconds / 86_400

    seconds_dur = seconds
    days_dur = seconds_dur / 86_400
    seconds_dur -= days_dur * 86_400
    hours_dur = seconds_dur / 3600
    seconds_dur -= hours_dur * 3600
    minutes_dur = seconds_dur / 60
    seconds_dur -= minutes_dur * 60
    uptime_s = "#{days_dur}:#{hours_dur}:#{minutes_dur}:#{seconds_dur}"

    boot_time = Time.now - seconds

    Uptime.new(
      boot_time,
      uptime_s,
      seconds,
      minutes,
      hours,
      days
    )
  end
end
w(opts = {}) click to toggle source
# File lib/kanrisuru/core/system.rb, line 536
def w(opts = {})
  users = opts[:users]
  command = Kanrisuru::Command.new('w -hi')

  command << users if Kanrisuru::Util.present?(users)

  execute(command)

  Kanrisuru::Result.new(command) do |cmd|
    result_string = cmd.raw_result.join
    rows = result_string.split("\n")

    rows.map do |row|
      values = *row.split(/\s+/, 8)
      UserLoggedIn.new(
        values[0],
        values[1],
        IPAddr.new(values[2]),
        values[3],
        values[4],
        values[5].to_f,
        values[6].to_f,
        values[7]
      )
    end
  end
end
who(opts = {}) click to toggle source
# File lib/kanrisuru/core/system.rb, line 564
def who(opts = {})
  w(opts)
end

Private Instance Methods

build_process_list(rows) click to toggle source
# File lib/kanrisuru/core/system.rb, line 680
def build_process_list(rows)
  rows.map do |row|
    values = *row.split(/\s+/, 15)
    values.shift if values[0] == ''

    ProcessInfo.new(
      values[0].to_i,
      values[1],
      values[2].to_i,
      values[3],
      values[4].to_i,
      values[5].to_i,
      values[6].to_f,
      values[7].to_f,
      values[8],
      values[9].to_i,
      values[10].to_i,
      values[11],
      parse_policy_abbr(values[11]),
      values[12],
      values[13]
    )
  end
end
format_shutdown_time(time) click to toggle source
# File lib/kanrisuru/core/system.rb, line 705
def format_shutdown_time(time)
  if time.instance_of?(Integer) || !/^[0-9]+$/.match(time).nil?
    "+#{time}"
  elsif !/^[0-9]{0,2}:[0-9]{0,2}$/.match(time).nil? || time == 'now'
    time
  else
    raise ArgumentError, 'Invalid time format'
  end
end
parse_last_date(string) click to toggle source
# File lib/kanrisuru/core/system.rb, line 644
def parse_last_date(string)
  tokens = string.split

  return if tokens.length < 4

  month_abbr = tokens[1]
  day = tokens[2]
  timestamp = tokens[3]
  year = tokens[4]

  DateTime.parse("#{day} #{month_abbr} #{year} #{timestamp}")
end
parse_lsof(line, char) click to toggle source
# File lib/kanrisuru/core/system.rb, line 640
def parse_lsof(line, char)
  line.split(char, 2)[1]
end
parse_policy_abbr(value) click to toggle source
# File lib/kanrisuru/core/system.rb, line 657
def parse_policy_abbr(value)
  case value
  when '-'
    'not reported'
  when 'TS'
    'SCHED_OTHER'
  when 'FF'
    'SCHED_FIFO'
  when 'RR'
    'SCHED_RR'
  when 'B'
    'SCHED_BATCH'
  when 'ISO'
    'SCHED_ISO'
  when 'IDL'
    'SCHED_IDLE'
  when 'DLN'
    'SCHED_DEADLINE'
  else
    'unknown value'
  end
end