class Rush::Connection::Local

Rush::Box uses a connection object to execute all rush commands. If the box is local, Rush::Connection::Local is created. The local connection is the heart of rush's internals. (Users of the rush shell or library need never access the connection object directly, so the docs herein are intended for developers wishing to modify rush.)

The local connection has a series of methods which do the actual work of modifying files, getting process lists, and so on. RushServer creates a local connection to handle incoming requests; the translation from a raw hash of parameters to an executed method is handled by Rush::Connection::Local#receive.

Public Instance Methods

alive?() click to toggle source

Local connections are always alive.

# File lib/rush/local.rb, line 401
def alive?
  true
end
append_to_file(full_path, contents) click to toggle source

Append contents to a file

# File lib/rush/local.rb, line 25
def append_to_file(full_path, contents)
  ::File.open(full_path, 'a') { |f| f.write contents }
  true
end
bash(command, user=nil, background=false, reset_environment=false) click to toggle source
# File lib/rush/local.rb, line 322
def bash(command, user=nil, background=false, reset_environment=false)
  return bash_background(command, user, reset_environment) if background
  sh = Session::Bash.new
  shell = reset_environment ? "env -i bash" : "bash"
  out, err = sh.execute sudo(user, shell), :stdin => command
  retval = sh.status
  sh.close!
  raise(Rush::BashFailed, err) if retval != 0
  out
end
bash_background(command, user, reset_environment) click to toggle source
# File lib/rush/local.rb, line 333
def bash_background(command, user, reset_environment)
  pid = fork do
    inpipe, outpipe = IO.pipe
    outpipe.write command
    outpipe.close
    STDIN.reopen(inpipe)
    close_all_descriptors([inpipe.to_i])
    shell = reset_environment ? "env -i bash" : "bash"
    exec sudo(user, shell)
  end
  Process::detach pid
  pid
end
chown( full_path, user=nil, group=nil, options={} ) click to toggle source

A frontend for FileUtils::chown

# File lib/rush/local.rb, line 155
def chown( full_path, user=nil, group=nil, options={} )
  if options[:recursive]
    FileUtils.chown_R(user, group, full_path, options)
  else
    FileUtils.chown(user, group, full_path, options)
  end
end
close_all_descriptors(keep_open = []) click to toggle source
# File lib/rush/local.rb, line 352
def close_all_descriptors(keep_open = [])
  3.upto(256) do |fd|
    next if keep_open.include?(fd)
    IO::new(fd).close rescue nil
  end
end
copy(src, dst) click to toggle source

Copy ane entry from one path to another.

# File lib/rush/local.rb, line 72
def copy(src, dst)
  raise(Rush::DoesNotExist, src) unless File.exists?(src)
  raise(Rush::DoesNotExist, File.dirname(dst)) unless File.exists?(File.dirname(dst))
  FileUtils.cp_r(src, dst)
  true
end
create_dir(full_path) click to toggle source

Create a dir.

# File lib/rush/local.rb, line 56
def create_dir(full_path)
  FileUtils.mkdir_p(full_path)
  true
end
destroy(full_path) click to toggle source

Destroy a file or dir.

# File lib/rush/local.rb, line 38
def destroy(full_path)
  raise "No." if full_path == '/'
  FileUtils.rm_rf(full_path)
  true
end
ensure_tunnel(options={}) click to toggle source

No-op for duck typing with remote connection.

# File lib/rush/local.rb, line 397
def ensure_tunnel(options={})
end
file_contents(full_path) click to toggle source

Read raw bytes from a file.

# File lib/rush/local.rb, line 31
def file_contents(full_path)
  ::File.read(full_path)
rescue Errno::ENOENT
  raise Rush::DoesNotExist, full_path
end
index(base_path, glob) click to toggle source

Get an index of files from the given path with the glob. Could return nested values if the glob contains a doubleglob. The return value is an array of full paths, with directories listed first.

# File lib/rush/local.rb, line 117
def index(base_path, glob)
  glob = '*' if glob == '' or glob.nil?
  dirs = []
  files = []
  ::Dir.chdir(base_path) do
    ::Dir.glob(glob).each do |fname|
      if ::File.directory?(fname)
        dirs << fname + '/'
      else
        files << fname
      end
    end
  end
  dirs.sort + files.sort
rescue Errno::ENOENT
  raise Rush::DoesNotExist, base_path
end
kill_process(pid, options={}) click to toggle source

Terminate a process, by pid.

# File lib/rush/local.rb, line 296
def kill_process(pid, options={})
  # time to wait before terminating the process, in seconds
  wait = options[:wait] || 3

  if wait > 0
    ::Process.kill('TERM', pid)

    # keep trying until it's dead (technique borrowed from god)
    begin
      Timeout.timeout(wait) do
        loop do
          return if !process_alive(pid)
          sleep 0.5
          ::Process.kill('TERM', pid) rescue nil
        end
      end
    rescue Timeout::Error
    end
  end

  ::Process.kill('KILL', pid) rescue nil

rescue Errno::ESRCH
  # if it's dead, great - do nothing
end
linux_processes() click to toggle source

Process list on Linux using /proc.

# File lib/rush/local.rb, line 185
def linux_processes
  ::Dir["/proc/*/stat"].
    select     { |file| file =~ /\/proc\/\d+\// }.
    inject([]) { |list, file| (list << read_proc_file(file)) rescue list }
end
ln(src, dst, options = {}) click to toggle source

Create a hard link to another file

# File lib/rush/local.rb, line 80
def ln(src, dst, options = {})
  raise(Rush::DoesNotExist, src) unless File.exists?(src)
  raise(Rush::DoesNotExist, File.dirname(dst)) unless File.exists?(File.dirname(dst))
  FileUtils.ln(src, dst, options)
  true
end
ln_s(src, dst, options = {}) click to toggle source

Create a symbolic link to another file

# File lib/rush/local.rb, line 88
def ln_s(src, dst, options = {})
  raise(Rush::DoesNotExist, src) unless File.exists?(src)
  raise(Rush::DoesNotExist, File.dirname(dst)) unless File.exists?(File.dirname(dst))
  FileUtils.ln_s(src, dst, options)
  true
end
os_x_processes() click to toggle source

Process list on OS X or other unixes without a /proc.

# File lib/rush/local.rb, line 239
def os_x_processes
  raw = os_x_raw_ps.split("\n").slice(1, 99999)
  raw.map do |line|
    parse_ps(line)
  end
end
os_x_raw_ps() click to toggle source

ps command used to generate list of processes on non-/proc unixes.

# File lib/rush/local.rb, line 247
def os_x_raw_ps
  `COLUMNS=9999 ps ax -o "pid uid ppid rss cpu command"`
end
parse_oleprocinfo(proc_info) click to toggle source

Parse the Windows OLE process info.

# File lib/rush/local.rb, line 276
def parse_oleprocinfo(proc_info)
  {
    pid: proc_info.ProcessId,
    uid: 0,
    command: proc_info.Name,
    cmdline: proc_info.CommandLine,
    mem: proc_info.MaximumWorkingSetSize,
    cpu: proc_info.KernelModeTime.to_i + proc_info.UserModeTime.to_i
  }
end
parse_ps(line) click to toggle source

Parse a single line of the ps command and return the values in a hash suitable for use in the Rush::Process#new.

# File lib/rush/local.rb, line 253
def parse_ps(line)
  m = line.split(" ", 6)
  {
    pid: m[0],
    uid: m[1],
    parent_pid: m[2].to_i,
    mem: m[3].to_i,
    cpu: m[4].to_i,
    cmdline: m[5],
    command: m[5].split(' ').first
  }
end
process_alive(pid) click to toggle source

Returns true if the specified pid is running.

# File lib/rush/local.rb, line 288
def process_alive(pid)
  ::Process.kill(0, pid)
  true
rescue Errno::ESRCH
  false
end
processes() click to toggle source

Get the list of processes as an array of hashes.

# File lib/rush/local.rb, line 174
def processes
  if ::File.directory? "/proc"
    resolve_unix_uids(linux_processes)
  elsif ::File.directory? "C:/WINDOWS"
    windows_processes
  else
    os_x_processes
  end.uniq
end
purge(full_path) click to toggle source

Purge the contents of a dir.

# File lib/rush/local.rb, line 45
def purge(full_path)
  raise "No." if full_path == '/'
  Dir.chdir(full_path) do
    all = Dir.glob("*", File::FNM_DOTMATCH).
      reject { |f| f == '.' or f == '..' }
    FileUtils.rm_rf all
  end
  true
end
read_archive(full_path) click to toggle source

Create an in-memory archive (tgz) of a file or dir, which can be transmitted to another server for a copy or move. Note that archive operations have the dir name implicit in the archive.

# File lib/rush/local.rb, line 103
def read_archive(full_path)
  `cd #{Rush.quote(::File.dirname(full_path))}; tar c #{Rush.quote(::File.basename(full_path))}`
end
read_proc_file(file) click to toggle source

Read a single file in /proc and store the parsed values in a hash suitable for use in the Rush::Process#new.

# File lib/rush/local.rb, line 212
def read_proc_file(file)
  stat_contents = ::File.read(file)
  stat_contents.gsub!(/\((.*)\)/, "")
  command = $1
  data = stat_contents.split(" ")
  uid = ::File.stat(file).uid
  pid = data[0]
  cmdline = ::File.read("/proc/#{pid}/cmdline").gsub(/\0/, ' ')
  parent_pid = data[2].to_i
  utime = data[12].to_i
  ktime = data[13].to_i
  vss = data[21].to_i / 1024
  rss = data[22].to_i * 4
  time = utime + ktime

  {
    :pid => pid,
    :uid => uid,
    :command => command,
    :cmdline => cmdline,
    :parent_pid => parent_pid,
    :mem => rss,
    :cpu => time
  }
end
receive(params) click to toggle source

RushServer uses this method to transform a hash (:action plus parameters specific to that action type) into a method call on the connection. The returned value must be text so that it can be transmitted across the wire as an HTTP response.

# File lib/rush/local.rb, line 368
def receive(params)
  case params[:action]
    when 'write_file'     then write_file(params[:full_path], params[:payload])
    when 'append_to_file' then append_to_file(params[:full_path], params[:payload])
    when 'file_contents'  then file_contents(params[:full_path])
    when 'destroy'        then destroy(params[:full_path])
    when 'purge'          then purge(params[:full_path])
    when 'create_dir'     then create_dir(params[:full_path])
    when 'rename'         then rename(params[:path], params[:name], params[:new_name])
    when 'copy'           then copy(params[:src], params[:dst])
    when 'ln'             then ln(params[:src], params[:dst], params[:options])
    when 'ln_s'           then ln_s(params[:src], params[:dst], params[:options])
    when 'read_archive'   then read_archive(params[:full_path])
    when 'write_archive'  then write_archive(params[:payload], params[:dir])
    when 'index'          then index(params[:base_path], params[:glob]).join("\n") + "\n"
    when 'stat'           then YAML.dump(stat(params[:full_path]))
    when 'set_access'     then set_access(params[:full_path], Rush::Access.from_hash(params))
    when 'chown'          then chown(params[:full_path], params[:user], params[:group], params[:options])
    when 'size'           then size(params[:full_path])
    when 'processes'      then YAML.dump(processes)
    when 'process_alive'  then process_alive(params[:pid]) ? '1' : '0'
    when 'kill_process'   then kill_process(params[:pid].to_i, YAML.load(params[:payload]))
    when 'bash'           then bash(params[:payload], params[:user], params[:background] == 'true', params[:reset_environment] == 'true')
  else
    raise UnknownAction
  end
end
rename(path, name, new_name) click to toggle source

Rename an entry within a dir.

# File lib/rush/local.rb, line 62
def rename(path, name, new_name)
  raise(Rush::NameCannotContainSlash, "#{path} rename #{name} to #{new_name}") if new_name.match(/\//)
  old_full_path = "#{path}/#{name}"
  new_full_path = "#{path}/#{new_name}"
  raise(Rush::NameAlreadyExists, "#{path} rename #{name} to #{new_name}") if ::File.exists?(new_full_path)
  FileUtils.mv(old_full_path, new_full_path)
  true
end
resolve_unix_uid_to_user(uid) click to toggle source

resolve uid to user

# File lib/rush/local.rb, line 200
def resolve_unix_uid_to_user(uid)
  uid = uid.to_i
  require 'etc'
  @uid_map ||= {}
  return @uid_map[uid] if @uid_map[uid]
  record = Etc.getpwuid(uid) rescue (return nil)
  @uid_map.merge uid => record.name
  record.name
end
resolve_unix_uids(list) click to toggle source
# File lib/rush/local.rb, line 191
def resolve_unix_uids(list)
  @uid_map = {} # reset the cache between uid resolutions.
  list.each do |process|
    process[:user] = resolve_unix_uid_to_user(process[:uid])
  end
  list
end
set_access(full_path, access) click to toggle source
# File lib/rush/local.rb, line 149
def set_access(full_path, access)
  access.apply(full_path)
end
size(full_path) click to toggle source

Fetch the size of a dir, since a standard file stat does not include the size of the contents.

# File lib/rush/local.rb, line 165
def size(full_path)
  if RUBY_PLATFORM.match(/darwin/)
    `find #{Rush.quote(full_path)} -print0 | xargs -0 stat -f%z`.split(/\n/).map(&:to_i).reduce(:+)
  else
    `du -sb #{Rush.quote(full_path)}`.match(/(\d+)/)[1].to_i
  end
end
stat(full_path) click to toggle source

Fetch stats (size, ctime, etc) on an entry. Size will not be accurate for dirs.

# File lib/rush/local.rb, line 136
def stat(full_path)
  s = ::File.stat(full_path)
  {
    :size => s.size,
    :ctime => s.ctime,
    :atime => s.atime,
    :mtime => s.mtime,
    :mode => s.mode
  }
rescue Errno::ENOENT
  raise Rush::DoesNotExist, full_path
end
sudo(user, shell) click to toggle source
# File lib/rush/local.rb, line 347
def sudo(user, shell)
  return shell if user.nil? || user.empty?
  "cd /; sudo -H -u #{user} \"#{shell}\""
end
windows_processes() click to toggle source

Process list on Windows.

# File lib/rush/local.rb, line 267
def windows_processes
  require 'win32ole'
  wmi = WIN32OLE.connect("winmgmts://")
  wmi.ExecQuery("select * from win32_process").map do |proc_info|
    parse_oleprocinfo(proc_info)
  end
end
write_archive(archive, dir) click to toggle source

Extract an in-memory archive to a dir.

# File lib/rush/local.rb, line 108
def write_archive(archive, dir)
  IO.popen("cd #{Rush::quote(dir)}; tar x", "w") do |p|
    p.write archive
  end
end
write_file(full_path, contents) click to toggle source

Write raw bytes to a file.

# File lib/rush/local.rb, line 19
def write_file(full_path, contents)
  ::File.open(full_path, 'w') { |f| f.write contents }
  true
end